/*
 * Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
 *
 * Released under the terms of the GNU GPL v2.0.
 */

#include <linux/module.h>
#include <linux/hash.h>
#include <net/tcp.h>
#include <scsi/scsi.h>

#include "iscsi.h"
#include "iscsi_dbg.h"
#include "iotype.h"

unsigned long debug_enable_flags;

static struct kmem_cache *iscsi_cmnd_cache;
static u8 dummy_data[PAGE_SIZE];

static int ctr_major;
static char ctr_name[] = "ietctl";
extern struct file_operations ctr_fops;

static u32 cmnd_write_size(struct iscsi_cmnd *cmnd)
{
	struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);

	if (hdr->flags & ISCSI_CMD_WRITE)
		return be32_to_cpu(hdr->data_length);
	return 0;
}

static u32 cmnd_read_size(struct iscsi_cmnd *cmnd)
{
	struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd);

	if (hdr->flags & ISCSI_CMD_READ) {
		struct iscsi_rlength_ahdr *ahdr =
			(struct iscsi_rlength_ahdr *)cmnd->pdu.ahs;

		if (!(hdr->flags & ISCSI_CMD_WRITE))
			return be32_to_cpu(hdr->data_length);
		if (ahdr && ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH)
			return be32_to_cpu(ahdr->read_length);
	}
	return 0;
}

static void iscsi_device_queue_cmnd(struct iscsi_cmnd *cmnd)
{
	set_cmnd_waitio(cmnd);
	wthread_queue(cmnd);
}

static void iscsi_scsi_queuecmnd(struct iscsi_cmnd *cmnd)
{
	struct iscsi_queue *queue = &cmnd->lun->queue;

	dprintk(D_GENERIC, "%p\n", cmnd);

	if ((cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) != ISCSI_CMD_UNTAGGED &&
	    (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) != ISCSI_CMD_SIMPLE) {
		cmnd->pdu.bhs.flags &= ~ISCSI_CMD_ATTR_MASK;
		cmnd->pdu.bhs.flags |= ISCSI_CMD_UNTAGGED;
	}

	spin_lock(&queue->queue_lock);

	set_cmnd_queued(cmnd);

	switch (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) {
	case ISCSI_CMD_UNTAGGED:
	case ISCSI_CMD_SIMPLE:
		if (!list_empty(&queue->wait_list) || queue->ordered_cmnd)
			goto pending;
		queue->active_cnt++;
		break;

	default:
		BUG();
	}
	spin_unlock(&queue->queue_lock);

	iscsi_device_queue_cmnd(cmnd);
	return;
 pending:
	assert(list_empty(&cmnd->list));

	list_add_tail(&cmnd->list, &queue->wait_list);
	spin_unlock(&queue->queue_lock);
	return;
}

static void iscsi_scsi_dequeuecmnd(struct iscsi_cmnd *cmnd)
{
	struct iscsi_queue *queue;

	if (!cmnd->lun)
		return;
	queue = &cmnd->lun->queue;
	spin_lock(&queue->queue_lock);
	switch (cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK) {
	case ISCSI_CMD_UNTAGGED:
	case ISCSI_CMD_SIMPLE:
		--queue->active_cnt;
		break;
	case ISCSI_CMD_ORDERED:
	case ISCSI_CMD_HEAD_OF_QUEUE:
	case ISCSI_CMD_ACA:
		BUG();
	default:
		/* Should the iscsi_scsi_queuecmnd func reject this ? */
		break;
	}

	while (!list_empty(&queue->wait_list)) {
		cmnd = list_entry(queue->wait_list.next, struct iscsi_cmnd, list);
		switch ((cmnd->pdu.bhs.flags & ISCSI_CMD_ATTR_MASK)) {
		case ISCSI_CMD_UNTAGGED:
		case ISCSI_CMD_SIMPLE:
			list_del_init(&cmnd->list);
			queue->active_cnt++;
			iscsi_device_queue_cmnd(cmnd);
			break;
		case ISCSI_CMD_ORDERED:
		case ISCSI_CMD_HEAD_OF_QUEUE:
		case ISCSI_CMD_ACA:
			BUG();
		}
	}

	spin_unlock(&queue->queue_lock);

	return;
}

/**
 * create a new command.
 *
 * iscsi_cmnd_create - 
 * @conn: ptr to connection (for i/o)
 *
 * @return    ptr to command or NULL
 */

struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn, int req)
{
	struct iscsi_cmnd *cmnd;

	/* TODO: async interface is necessary ? */
	cmnd = kmem_cache_alloc(iscsi_cmnd_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);
	if (req)
		list_add_tail(&cmnd->conn_list, &conn->pdu_list);
	spin_unlock(&conn->list_lock);
	cmnd->tio = NULL;

	dprintk(D_GENERIC, "%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 iscsi_cmnd *iscsi_cmnd_create_rsp_cmnd(struct iscsi_cmnd *cmnd, int final)
{
	struct iscsi_cmnd *rsp = cmnd_alloc(cmnd->conn, 0);

	if (final)
		set_cmnd_final(rsp);
	list_add_tail(&rsp->pdu_list, &cmnd->pdu_list);
	rsp->req = cmnd;
	return rsp;
}

static struct iscsi_cmnd *get_rsp_cmnd(struct iscsi_cmnd *req)
{
	return list_entry(req->pdu_list.prev, struct iscsi_cmnd, pdu_list);
}

static void iscsi_cmnds_init_write(struct list_head *send)
{
	struct iscsi_cmnd *cmnd = list_entry(send->next, struct iscsi_cmnd, 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 iscsi_cmnd, list);

		dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));

		list_del_init(&cmnd->list);
		assert(conn == cmnd->conn);
		list_add_tail(&cmnd->list, &conn->write_list);
	}

	spin_unlock(&conn->list_lock);

	nthread_wakeup(conn->session->target);
}

static void iscsi_cmnd_init_write(struct iscsi_cmnd *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",
			cmnd_itt(cmnd), cmnd_ttt(cmnd), cmnd_opcode(cmnd),
			cmnd_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));

		assert(list_empty(&cmnd->list));
	}
	list_add(&cmnd->list, &head);
	iscsi_cmnds_init_write(&head);
}

static void do_send_data_rsp(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;
	struct iscsi_cmnd *data_cmnd;
	struct tio *tio = cmnd->tio;
	struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);
	struct iscsi_data_in_hdr *rsp;
	u32 pdusize, expsize, scsisize, size, offset, sn;
	LIST_HEAD(send);

	dprintk(D_GENERIC, "%p\n", cmnd);
	pdusize = conn->session->param.max_xmit_data_length;
	expsize = cmnd_read_size(cmnd);
	size = min(expsize, tio->size);
	offset = 0;
	sn = 0;

	while (1) {
		data_cmnd = iscsi_cmnd_create_rsp_cmnd(cmnd, size <= pdusize);
		tio_get(tio);
		data_cmnd->tio = tio;
		rsp = (struct iscsi_data_in_hdr *)&data_cmnd->pdu.bhs;

		rsp->opcode = ISCSI_OP_SCSI_DATA_IN;
		rsp->itt = req->itt;
		rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);
		rsp->buffer_offset = offset;
		rsp->data_sn = cpu_to_be32(sn);

		if (size <= pdusize) {
			data_cmnd->pdu.datasize = size;
			rsp->flags = ISCSI_FLG_FINAL | ISCSI_FLG_STATUS;

			scsisize = tio->size;
			if (scsisize < expsize) {
				rsp->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
				size = expsize - scsisize;
			} else if (scsisize > expsize) {
				rsp->flags |= ISCSI_FLG_RESIDUAL_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 iscsi_cmnd *create_scsi_rsp(struct iscsi_cmnd *req)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
	struct iscsi_scsi_rsp_hdr *rsp_hdr;

	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);

	rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
	rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
	rsp_hdr->flags = ISCSI_FLG_FINAL;
	rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
	rsp_hdr->cmd_status = SAM_STAT_GOOD;
	rsp_hdr->itt = req_hdr->itt;

	return rsp;
}

static struct iscsi_cmnd *create_sense_rsp(struct iscsi_cmnd *req,
					   u8 sense_key, u8 asc, u8 ascq)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_scsi_rsp_hdr *rsp_hdr;
	struct tio *tio;
	struct iscsi_sense_data *sense;

	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);

	rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
	rsp_hdr->opcode = ISCSI_OP_SCSI_RSP;
	rsp_hdr->flags = ISCSI_FLG_FINAL;
	rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED;
	rsp_hdr->cmd_status = SAM_STAT_CHECK_CONDITION;
	rsp_hdr->itt = cmnd_hdr(req)->itt;

	tio = rsp->tio = tio_alloc(1);
	sense = (struct iscsi_sense_data *) page_address(tio->pvec[0]);
	assert(sense);
	clear_page(sense);
	sense->length = cpu_to_be16(14);
	sense->data[0] = 0xf0;
	sense->data[2] = sense_key;
	sense->data[7] = 6;	// Additional sense length
	sense->data[12] = asc;
	sense->data[13] = ascq;

	rsp->pdu.datasize = sizeof(struct iscsi_sense_data) + 14;
	tio->size = (rsp->pdu.datasize + 3) & -4;
	tio->offset = 0;

	return rsp;
}

void send_scsi_rsp(struct iscsi_cmnd *req, int (*func)(struct iscsi_cmnd *))
{
	struct iscsi_cmnd *rsp;
	struct iscsi_scsi_rsp_hdr *rsp_hdr;
	u32 size;
	int ret = func(req);

	switch (ret) {
	case 0:
	case -EBUSY:
		rsp = create_scsi_rsp(req);
		rsp_hdr = (struct iscsi_scsi_rsp_hdr *) &rsp->pdu.bhs;
		if ((size = cmnd_read_size(req)) != 0) {
			rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
			rsp_hdr->residual_count = cpu_to_be32(size);
		}
		if (ret == -EBUSY)
			rsp_hdr->cmd_status = SAM_STAT_RESERVATION_CONFLICT;
		break;
	case -EIO:
		/* Medium Error/Write Fault */
		rsp = create_sense_rsp(req, MEDIUM_ERROR, 0x03, 0x0);
		break;
	default:
		rsp = create_sense_rsp(req, ILLEGAL_REQUEST, 0x24, 0x0);
	}
	iscsi_cmnd_init_write(rsp);
}

void send_data_rsp(struct iscsi_cmnd *req, int (*func)(struct iscsi_cmnd *))
{
	struct iscsi_cmnd *rsp;

	switch (func(req)) {
	case 0:
		do_send_data_rsp(req);
		return;
	case -EIO:
		/* Medium Error/Unrecovered Read Error */
		rsp = create_sense_rsp(req, MEDIUM_ERROR, 0x11, 0x0);
		break;
	default:
		rsp = create_sense_rsp(req, ILLEGAL_REQUEST, 0x24, 0x0);
	}
	iscsi_cmnd_init_write(rsp);
}

/**
 * Free a command.
 * Also frees the additional header.
 *
 * iscsi_cmnd_remove - 
 * @cmnd: ptr to command
 */

static void iscsi_cmnd_remove(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn;

	if (!cmnd)
		return;
	dprintk(D_GENERIC, "%p\n", cmnd);
	conn = cmnd->conn;
	kfree(cmnd->pdu.ahs);

	if (!list_empty(&cmnd->list)) {
		struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd);

		eprintk("cmnd %p still on some list?, %x, %x, %x, %x, %x, %x, %x %lx\n",
			cmnd, req->opcode, req->scb[0], req->flags, req->itt,
			be32_to_cpu(req->data_length),
			req->cmd_sn, be32_to_cpu(cmnd->pdu.datasize),
			conn->state);

		if (cmnd->req) {
			struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd->req);
			eprintk("%p %x %u\n", req, req->opcode, req->scb[0]);
		}
		dump_stack();
		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->tio)
		tio_put(cmnd->tio);

	kmem_cache_free(iscsi_cmnd_cache, cmnd);
}

static void cmnd_skip_pdu(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;
	struct tio *tio = cmnd->tio;
	char *addr;
	u32 size;
	int i;

	eprintk("%x %x %x %u\n", cmnd_itt(cmnd), cmnd_opcode(cmnd),
		cmnd_hdr(cmnd)->scb[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 iscsi_cmnd *req, int reason)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_reject_hdr *rsp_hdr;
	struct tio *tio;
	char *addr;

	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 iscsi_cmnd *cmnd, int set_stat_sn)
{
	struct iscsi_conn *conn = cmnd->conn;
	struct iscsi_session *sess = conn->session;

	if (set_stat_sn)
		cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn++);
	cmnd->pdu.bhs.exp_sn = cpu_to_be32(sess->exp_cmd_sn);
	cmnd->pdu.bhs.max_sn = cpu_to_be32(sess->exp_cmd_sn + sess->max_queued_cmnds);
}

static void update_stat_sn(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;
	u32 exp_stat_sn;

	cmnd->pdu.bhs.exp_sn = exp_stat_sn = be32_to_cpu(cmnd->pdu.bhs.exp_sn);
	dprintk(D_GENERIC, "%x,%x\n", cmnd_opcode(cmnd), exp_stat_sn);
	if ((int)(exp_stat_sn - conn->exp_stat_sn) > 0 &&
	    (int)(exp_stat_sn - conn->stat_sn) <= 0) {
		// free pdu resources
		cmnd->conn->exp_stat_sn = exp_stat_sn;
	}
}

static int check_cmd_sn(struct iscsi_cmnd *cmnd)
{
	struct iscsi_session *session = cmnd->conn->session;
	u32 cmd_sn;

	cmnd->pdu.bhs.sn = cmd_sn = be32_to_cpu(cmnd->pdu.bhs.sn);
	dprintk(D_GENERIC, "%d(%d)\n", cmd_sn, session->exp_cmd_sn);
	if ((s32)(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 iscsi_cmnd *__cmnd_find_hash(struct iscsi_session *session, u32 itt, u32 ttt)
{
	struct list_head *head;
	struct iscsi_cmnd *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 iscsi_cmnd *cmnd_find_hash(struct iscsi_session *session, u32 itt, u32 ttt)
{
	struct iscsi_cmnd *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 iscsi_cmnd *cmnd)
{
	struct iscsi_session *session = cmnd->conn->session;
	struct iscsi_cmnd *tmp;
	struct list_head *head;
	int err = 0;
	u32 itt = cmnd->pdu.bhs.itt;

	dprintk(D_GENERIC, "%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_cmnd_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 iscsi_cmnd *cmnd)
{
	list_del(&cmnd->hash_list);
}

static void cmnd_remove_hash(struct iscsi_cmnd *cmnd)
{
	struct iscsi_session *session = cmnd->conn->session;
	struct iscsi_cmnd *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, cmnd_itt(cmnd));

	spin_unlock(&session->cmnd_hash_lock);
}

static void cmnd_skip_data(struct iscsi_cmnd *req)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_scsi_rsp_hdr *rsp_hdr;
	u32 size;

	rsp = get_rsp_cmnd(req);
	rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs;
	if (cmnd_opcode(rsp) != ISCSI_OP_SCSI_RSP) {
		eprintk("unexpected response command %u\n", cmnd_opcode(rsp));
		return;
	}

	size = cmnd_write_size(req);
	if (size) {
		rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
		rsp_hdr->residual_count = cpu_to_be32(size);
	}
	size = cmnd_read_size(req);
	if (size) {
		if (cmnd_hdr(req)->flags & ISCSI_CMD_WRITE) {
			rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW;
			rsp_hdr->bi_residual_count = cpu_to_be32(size);
		} else {
			rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW;
			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 tio *tio, u32 offset, u32 size)
{
	int idx, i;
	char *addr;

	dprintk(D_GENERIC, "%p %u,%u\n", tio, offset, size);
	offset += tio->offset;

	if (!(offset < tio->offset + tio->size) ||
	    !(offset + size <= tio->offset + tio->size)) {
		eprintk("%u %u %u %u", offset, size, tio->offset, tio->size);
		return -EIO;
	}
	assert(offset < tio->offset + tio->size);
	assert(offset + size <= tio->offset + tio->size);

	idx = offset >> PAGE_CACHE_SHIFT;
	offset &= ~PAGE_CACHE_MASK;

	conn->read_msg.msg_iov = conn->read_iov;
	conn->read_size = size = (size + 3) & -4;
	conn->read_overflow = 0;

	i = 0;
	while (1) {
		assert(tio->pvec[idx]);
		addr = page_address(tio->pvec[idx]);
		assert(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 set_offset_and_length(struct iet_volume *lu, u8 *cmd, loff_t *off, u32 *len)
{
	assert(lu);

	switch (cmd[0]) {
	case READ_6:
	case WRITE_6:
		*off = ((cmd[1] & 0x1f) << 16) + (cmd[2] << 8) + cmd[3];
		*len = cmd[4];
		if (!*len)
			*len = 256;
		break;
	case READ_10:
	case WRITE_10:
	case WRITE_VERIFY:
		*off = (u32)cmd[2] << 24 | (u32)cmd[3] << 16 |
			(u32)cmd[4] << 8 | (u32)cmd[5];
		*len = (cmd[7] << 8) + cmd[8];
		break;
	case READ_16:
	case WRITE_16:
		*off = (u64)cmd[2] << 56 | (u64)cmd[3] << 48 |
			(u64)cmd[4] << 40 | (u64)cmd[5] << 32 |
			(u64)cmd[6] << 24 | (u64)cmd[7] << 16 |
			(u64)cmd[8] << 8 | (u64)cmd[9];
		*len = (u32)cmd[10] << 24 | (u32)cmd[11] << 16 |
			(u32)cmd[12] << 8 | (u32)cmd[13];
		break;
	default:
		BUG();
	}

	*off <<= lu->blk_shift;
	*len <<= lu->blk_shift;
}

static u32 translate_lun(u16 * data)
{
	u8 *p = (u8 *) data;
	u32 lun = ~0U;

	switch (*p >> 6) {
	case 0:
		lun = p[1];
		break;
	case 1:
		lun = (0x3f & p[0]) << 8 | p[1];
		break;
	case 2:
	case 3:
	default:
		eprintk("%u %u %u %u\n", data[0], data[1], data[2], data[3]);
		break;
	}

	return lun;
}

static void send_r2t(struct iscsi_cmnd *req)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_r2t_hdr *rsp_hdr;
	u32 length, offset, burst;
	LIST_HEAD(send);

	length = req->r2t_length;
	burst = req->conn->session->param.max_burst_length;
	offset = be32_to_cpu(cmnd_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_hdr *)&rsp->pdu.bhs;
		rsp_hdr->opcode = ISCSI_OP_R2T;
		rsp_hdr->flags = ISCSI_FLG_FINAL;
		memcpy(rsp_hdr->lun, cmnd_hdr(req)->lun, 8);
		rsp_hdr->itt = cmnd_hdr(req)->itt;
		rsp_hdr->r2t_sn = cpu_to_be32(req->r2t_sn++);
		rsp_hdr->buffer_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(D_WRITE, "%x %u %u %u %u\n", cmnd_itt(req),
			be32_to_cpu(rsp_hdr->data_length),
			be32_to_cpu(rsp_hdr->buffer_offset),
			be32_to_cpu(rsp_hdr->r2t_sn), 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_exec(struct iscsi_cmnd *cmnd)
{
	if (cmnd->r2t_length) {
		if (!cmnd->is_unsolicited_data)
			send_r2t(cmnd);
	} else {
		if (cmnd->lun) {
			iscsi_scsi_queuecmnd(cmnd);
		} else {
			iscsi_device_queue_cmnd(cmnd);
		}
	}
}

static int noop_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
{
	u32 size, tmp;
	int i, err = 0;

	if (cmnd_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", cmnd_itt(cmnd));
		err = -ISCSI_REASON_PROTOCOL_ERROR;
		goto out;
	}

	if (cmnd_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);
		if (err)
			goto out;
	} else if ((err = cmnd_insert_hash(cmnd)) < 0) {
		eprintk("ignore this request %x\n", cmnd_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);

			assert(pg_cnt < ISCSI_CONN_IOV_MAX);
			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(u32, size, sizeof(dummy_data));
				conn->read_iov[i].iov_len = tmp;
				conn->read_size += tmp;
				size -= tmp;
			}
		}
		assert(!size);
		conn->read_overflow = size;
		conn->read_msg.msg_iovlen = i;
	}

out:
	return err;
}

static u32 get_next_ttt(struct iscsi_session *session)
{
	u32 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 iscsi_cmnd *req)
{
	struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);

	dprintk(D_GENERIC, "scsi command: %02x\n", req_hdr->scb[0]);

	req->lun = volume_get(conn->session->target, translate_lun(req_hdr->lun));
	if (!req->lun) {
		switch (req_hdr->scb[0]) {
		case INQUIRY:
		case REPORT_LUNS:
			break;
		default:
			eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
			create_sense_rsp(req, ILLEGAL_REQUEST, 0x25, 0x0);
			cmnd_skip_data(req);
			goto out;
		}
	} else
		set_cmnd_lunit(req);

	switch (req_hdr->scb[0]) {
	case SERVICE_ACTION_IN:
		if ((req_hdr->scb[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:
	{
		if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) {
			/* unexpected unsolicited data */
			eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
			create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc);
			cmnd_skip_data(req);
		}
		break;
	}
	case READ_6:
	case READ_10:
	case READ_16:
	{
		loff_t offset;
		u32 length;

		if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) {
			/* unexpected unsolicited data */
			eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);
			create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc);
			cmnd_skip_data(req);
			break;
		}

		set_offset_and_length(req->lun, req_hdr->scb, &offset, &length);
		req->tio = tio_alloc(get_pgcnt(length, offset));
		tio_set(req->tio, length, offset);
		break;
	}
	case WRITE_6:
	case WRITE_10:
	case WRITE_16:
	case WRITE_VERIFY:
	{
		struct iscsi_sess_param *param = &conn->session->param;
		loff_t offset;
		u32 length;

		req->r2t_length = be32_to_cpu(req_hdr->data_length) - req->pdu.datasize;
		req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL);
		req->target_task_tag = get_next_ttt(conn->session);

		if (LUReadonly(req->lun)) {
			create_sense_rsp(req, DATA_PROTECT, 0x27, 0x0);
			cmnd_skip_data(req);
			break;
		}

		if (!param->immediate_data && req->pdu.datasize)
			eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);

		if (param->initial_r2t && !(req_hdr->flags & ISCSI_CMD_FINAL))
			eprintk("%x %x\n", cmnd_itt(req), req_hdr->scb[0]);

		if (req_hdr->scb[0] == WRITE_VERIFY && req_hdr->scb[1] & 0x02)
			eprintk("Verification is ignored %x\n", cmnd_itt(req));

		set_offset_and_length(req->lun, req_hdr->scb, &offset, &length);
		if (cmnd_write_size(req) != length)
			eprintk("%x %u %u\n", cmnd_itt(req), cmnd_write_size(req), length);

		req->tio = tio_alloc(get_pgcnt(length, offset));
		tio_set(req->tio, length, offset);

		if (req->pdu.datasize) {
			if (cmnd_recv_pdu(conn, req->tio, 0, req->pdu.datasize) < 0)
				assert(0);
		}
		break;
	}
	error:
	default:
		eprintk("Unsupported %x\n", req_hdr->scb[0]);
		create_sense_rsp(req, ILLEGAL_REQUEST, 0x20, 0x0);
		cmnd_skip_data(req);
		break;
	}

out:
	return;
}

static void data_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
{
	struct iscsi_data_out_hdr *req = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
	struct iscsi_cmnd *scsi_cmnd = NULL;
	u32 offset = be32_to_cpu(req->buffer_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",
			cmnd_itt(cmnd), cmnd_ttt(cmnd));
		goto skip_data;
	}

	if (scsi_cmnd->r2t_length < cmnd->pdu.datasize) {
		eprintk("invalid data len %x %u %u\n",
			cmnd_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", cmnd_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_FLG_FINAL) {
			eprintk("unexpected data from %x %x\n",
				cmnd_itt(cmnd), cmnd_ttt(cmnd));
			goto skip_data;
		}
	}

	dprintk(D_WRITE, "%u %p %p %p %u %u\n", req->ttt, cmnd, scsi_cmnd,
		scsi_cmnd->tio, offset, cmnd->pdu.datasize);

	if (cmnd_recv_pdu(conn, scsi_cmnd->tio, 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 iscsi_cmnd *cmnd)
{
	struct iscsi_data_out_hdr *req = (struct iscsi_data_out_hdr *) &cmnd->pdu.bhs;
	struct iscsi_cmnd *scsi_cmnd;
	u32 offset;

	assert(cmnd);
	scsi_cmnd = cmnd->req;
	assert(scsi_cmnd);

	if (conn->read_overflow) {
		eprintk("%x %u\n", cmnd_itt(cmnd), conn->read_overflow);
		assert(scsi_cmnd->tio);
		offset = be32_to_cpu(req->buffer_offset);
		offset += cmnd->pdu.datasize - conn->read_overflow;
		if (cmnd_recv_pdu(conn, scsi_cmnd->tio, offset, conn->read_overflow) < 0)
			assert(0);
		return;
	}

	if (req->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) {
		if (req->flags & ISCSI_FLG_FINAL) {
			scsi_cmnd->is_unsolicited_data = 0;
			if (!cmnd_pending(scsi_cmnd))
				scsi_cmnd_exec(scsi_cmnd);
		}
	} else {
		/* TODO : proper error handling */
		if (!(req->flags & ISCSI_FLG_FINAL) && scsi_cmnd->r2t_length == 0)
			eprintk("initiator error %x\n", cmnd_itt(scsi_cmnd));

		if (!(req->flags & ISCSI_FLG_FINAL))
			goto out;

		scsi_cmnd->outstanding_r2t--;

		if (scsi_cmnd->r2t_length == 0)
			assert(list_empty(&scsi_cmnd->pdu_list));

		scsi_cmnd_exec(scsi_cmnd);
	}

out:
	iscsi_cmnd_remove(cmnd);
	return;
}

static int __cmnd_abort(struct iscsi_cmnd *cmnd)
{
	if (cmnd_waitio(cmnd))
		return -ISCSI_RESPONSE_UNKNOWN_TASK;

	if (cmnd->conn->read_cmnd != cmnd)
		cmnd_release(cmnd, 1);
	else if (cmnd_rxstart(cmnd))
		set_cmnd_tmfabort(cmnd);
	else
		return -ISCSI_RESPONSE_UNKNOWN_TASK;

	return 0;
}

static int cmnd_abort(struct iscsi_session *session, u32 itt)
{
	struct iscsi_cmnd *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", cmnd_itt(cmnd), cmnd_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 iscsi_cmnd *req, u32 lun, int all)
{
	struct iscsi_target *target = req->conn->session->target;
	struct iscsi_session *session;
	struct iscsi_conn *conn;
	struct iscsi_cmnd *cmnd, *tmp;
	struct iet_volume *volumes;

	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(cmnd_hdr(cmnd)->lun) == lun)
					__cmnd_abort(cmnd);
			}
		}
	}

	list_for_each_entry(volumes, &target->volumes, list)
		if (all || volumes->lun == lun)
			/* force release */
			volume_release(volumes, 0, 1);

	return 0;
}

static void task_set_abort(struct iscsi_cmnd *req)
{
	struct iscsi_session *session = req->conn->session;
	struct iscsi_conn *conn;
	struct iscsi_cmnd *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 inline char *tmf_desc(int fun)
{
	static char *tmf_desc[] = {
		"Unknown Function",
		"Abort Task",
		"Abort Task Set",
		"Clear ACA",
		"Clear Task Set",
		"Logical Unit Reset",
		"Target Warm Reset",
		"Target Cold Reset",
		"Task Reassign",
        };

	if ((fun < ISCSI_FUNCTION_ABORT_TASK) || 
				(fun > ISCSI_FUNCTION_TASK_REASSIGN))
		fun = 0;

	return tmf_desc[fun];
}

static inline char *rsp_desc(int rsp)
{
	static char *rsp_desc[] = {
		"Function Complete",
		"Unknown Task",
		"Unknown LUN",
		"Task Allegiant",
		"Failover Unsupported",
		"Function Unsupported",
		"No Authorization",
		"Function Rejected",
		"Unknown Response",
	};

	if (((rsp < ISCSI_RESPONSE_FUNCTION_COMPLETE) ||
			(rsp > ISCSI_RESPONSE_NO_AUTHORIZATION)) &&
			(rsp != ISCSI_RESPONSE_FUNCTION_REJECTED))
		rsp = 8;
	else if (rsp == ISCSI_RESPONSE_FUNCTION_REJECTED)
		rsp = 7;

	return rsp_desc[rsp];
}

static void execute_task_management(struct iscsi_cmnd *req)
{
	struct iscsi_conn *conn = req->conn;
	struct iscsi_session *session = conn->session;
	struct iscsi_target *target = session->target;
	struct iscsi_cmnd *rsp;
	struct iscsi_task_mgt_hdr *req_hdr = (struct iscsi_task_mgt_hdr *)&req->pdu.bhs;
	struct iscsi_task_rsp_hdr *rsp_hdr;
	u32 lun;
	int err, function = req_hdr->function & ISCSI_FUNCTION_MASK;

	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
	rsp_hdr = (struct iscsi_task_rsp_hdr *)&rsp->pdu.bhs;

	rsp_hdr->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP;
	rsp_hdr->flags = ISCSI_FLG_FINAL;
	rsp_hdr->itt = req_hdr->itt;
	rsp_hdr->response = ISCSI_RESPONSE_FUNCTION_COMPLETE;

	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:
	iprintk("%s (%02x) issued on tid:%d lun:%d by sid:%llu (%s)\n",
				tmf_desc(function), function, target->tid,
				translate_lun(req_hdr->lun), session->sid,
				rsp_desc(rsp_hdr->response));

	iscsi_cmnd_init_write(rsp);
}

static void noop_out_exec(struct iscsi_cmnd *req)
{
	struct iscsi_cmnd *rsp;
	struct iscsi_nop_in_hdr *rsp_hdr;

	if (cmnd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) {
		rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);

		rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs;
		rsp_hdr->opcode = ISCSI_OP_NOOP_IN;
		rsp_hdr->flags = ISCSI_FLG_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;
		}

		assert(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 iscsi_cmnd *req)
{
	struct iscsi_logout_req_hdr *req_hdr;
	struct iscsi_cmnd *rsp;
	struct iscsi_logout_rsp_hdr *rsp_hdr;

	req_hdr = (struct iscsi_logout_req_hdr *)&req->pdu.bhs;
	rsp = iscsi_cmnd_create_rsp_cmnd(req, 1);
	rsp_hdr = (struct iscsi_logout_rsp_hdr *)&rsp->pdu.bhs;
	rsp_hdr->opcode = ISCSI_OP_LOGOUT_RSP;
	rsp_hdr->flags = ISCSI_FLG_FINAL;
	rsp_hdr->itt = req_hdr->itt;
	set_cmnd_close(rsp);
	iscsi_cmnd_init_write(rsp);
}

static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd)
{
	dprintk(D_GENERIC, "%p,%x,%u\n", cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn);

	switch (cmnd_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_TASK_MGT_MSG:
		execute_task_management(cmnd);
		break;
	case ISCSI_OP_LOGOUT_CMD:
		logout_exec(cmnd);
		break;
	case ISCSI_OP_SCSI_REJECT:
		iscsi_cmnd_init_write(get_rsp_cmnd(cmnd));
		break;
	case ISCSI_OP_TEXT_CMD:
	case ISCSI_OP_SNACK_CMD:
		break;
	default:
		eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
		break;
	}
}

static void __cmnd_send_pdu(struct iscsi_conn *conn, struct tio *tio, u32 offset, u32 size)
{
	dprintk(D_GENERIC, "%p %u,%u\n", tio, offset, size);
	offset += tio->offset;

	assert(offset <= tio->offset + tio->size);
	assert(offset + size <= tio->offset + tio->size);

	conn->write_tcmnd = tio;
	conn->write_offset = offset;
	conn->write_size += size;
}

static void cmnd_send_pdu(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd)
{
	u32 size;
	struct tio *tio;

	if (!cmnd->pdu.datasize)
		return;

	size = (cmnd->pdu.datasize + 3) & -4;
	tio = cmnd->tio;
	assert(tio);
	assert(tio->size == size);
	__cmnd_send_pdu(conn, tio, 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 iscsi_cmnd *cmnd, int force)
{
	struct iscsi_cmnd *req, *rsp;
	int is_last = 0;

	if (!cmnd)
		return;

/* 	eprintk("%x %lx %d\n", cmnd_opcode(cmnd), cmnd->flags, force); */

	req = cmnd->req;
	is_last = cmnd_final(cmnd);

	if (force) {
		while (!list_empty(&cmnd->pdu_list)) {
			rsp = list_entry(cmnd->pdu_list.next, struct iscsi_cmnd, pdu_list);
			list_del_init(&rsp->list);
			list_del(&rsp->pdu_list);
			iscsi_cmnd_remove(rsp);
		}
		list_del_init(&cmnd->list);
	} else
		if (cmnd_queued(cmnd))
			iscsi_scsi_dequeuecmnd(cmnd);

	if (cmnd_hashed(cmnd))
		cmnd_remove_hash(cmnd);

	if (cmnd_lunit(cmnd)) {
		assert(cmnd->lun);
		volume_put(cmnd->lun);
	}

	list_del_init(&cmnd->pdu_list);
	iscsi_cmnd_remove(cmnd);

	if (is_last) {
		assert(!force);
		assert(req);
		cmnd_release(req, 0);
	}

	return;
}

void cmnd_tx_start(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;
	struct iovec *iop;

	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
	assert(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 (cmnd_opcode(cmnd)) {
	case ISCSI_OP_NOOP_IN:
		cmnd_set_sn(cmnd, 1);
		cmnd_send_pdu(conn, cmnd);
		break;
	case ISCSI_OP_SCSI_RSP:
		cmnd_set_sn(cmnd, 1);
		cmnd_send_pdu(conn, cmnd);
		break;
	case ISCSI_OP_SCSI_TASK_MGT_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_in_hdr *rsp = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
		u32 offset;

		cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0);
		offset = rsp->buffer_offset;
		rsp->buffer_offset = cpu_to_be32(offset);
		__cmnd_send_pdu(conn, cmnd->tio, 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.sn = cpu_to_be32(conn->stat_sn);
		break;
	case ISCSI_OP_ASYNC_MSG:
		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", cmnd_opcode(cmnd));
		break;
	}

	iop->iov_len = 0;
	// move this?
	conn->write_size = (conn->write_size + 3) & -4;
	iscsi_dump_pdu(&cmnd->pdu);
}

void cmnd_tx_end(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;

	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
	switch (cmnd_opcode(cmnd)) {
	case ISCSI_OP_NOOP_IN:
	case ISCSI_OP_SCSI_RSP:
	case ISCSI_OP_SCSI_TASK_MGT_RSP:
	case ISCSI_OP_TEXT_RSP:
	case ISCSI_OP_R2T:
	case ISCSI_OP_ASYNC_MSG:
	case ISCSI_OP_REJECT:
	case ISCSI_OP_SCSI_DATA_IN:
	case ISCSI_OP_LOGOUT_RSP:
		break;
	default:
		eprintk("unexpected cmnd op %x\n", cmnd_opcode(cmnd));
		assert(0);
		break;
	}

	if (cmnd_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 iscsi_cmnd *cmnd)
{
	struct iscsi_session *session = cmnd->conn->session;
	struct list_head *entry;
	u32 cmd_sn;

	dprintk(D_GENERIC, "%p:%x %u,%u\n",
		cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn, session->exp_cmd_sn);

	if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) {
		iscsi_cmnd_exec(cmnd);
		return;
	}

	cmd_sn = cmnd->pdu.bhs.sn;
	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 iscsi_cmnd, list);
			if (cmnd->pdu.bhs.sn != cmd_sn)
				break;
/* 			eprintk("find out-of-order %x %u %u\n", */
/* 				cmnd_itt(cmnd), cmd_sn, cmnd->pdu.bhs.sn); */
			list_del_init(&cmnd->list);
			clear_cmnd_pending(cmnd);
		}
	} else {
/* 		eprintk("out-of-order %x %u %u\n", */
/* 			cmnd_itt(cmnd), cmd_sn, session->exp_cmd_sn); */

		set_cmnd_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 iscsi_cmnd *tmp = list_entry(entry, struct iscsi_cmnd, list);
			if (before(cmd_sn, tmp->pdu.bhs.sn))
				break;
		}

		assert(list_empty(&cmnd->list));

		list_add_tail(&cmnd->list, entry);
	}
}

static int check_segment_length(struct iscsi_cmnd *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("data too long %x %u %u\n", cmnd_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 iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;
	int err = 0;

	iscsi_dump_pdu(&cmnd->pdu);

	set_cmnd_rxstart(cmnd);
	if (check_segment_length(cmnd) < 0)
		return;

	switch (cmnd_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_TASK_MGT_MSG:
		err = cmnd_insert_hash(cmnd);
		break;
	case ISCSI_OP_SCSI_DATA_OUT:
		data_out_start(conn, cmnd);
		break;
	case ISCSI_OP_LOGOUT_CMD:
		err = cmnd_insert_hash(cmnd);
		break;
	case ISCSI_OP_TEXT_CMD:
	case ISCSI_OP_SNACK_CMD:
		err = -ISCSI_REASON_UNSUPPORTED_COMMAND;
		break;
	default:
		err = -ISCSI_REASON_UNSUPPORTED_COMMAND;
		break;
	}

	if (err < 0) {
		eprintk("%x %x %d\n", cmnd_opcode(cmnd), cmnd_itt(cmnd), err);
		iscsi_cmnd_reject(cmnd, -err);
	}
}

void cmnd_rx_end(struct iscsi_cmnd *cmnd)
{
	struct iscsi_conn *conn = cmnd->conn;

	if (cmnd_tmfabort(cmnd)) {
		cmnd_release(cmnd, 1);
		return;
	}

	dprintk(D_GENERIC, "%p:%x\n", cmnd, cmnd_opcode(cmnd));
	switch (cmnd_opcode(cmnd)) {
	case ISCSI_OP_SCSI_REJECT:
	case ISCSI_OP_NOOP_OUT:
	case ISCSI_OP_SCSI_CMD:
	case ISCSI_OP_SCSI_TASK_MGT_MSG:
	case ISCSI_OP_TEXT_CMD:
	case ISCSI_OP_LOGOUT_CMD:
		iscsi_session_push_cmnd(cmnd);
		break;
	case ISCSI_OP_SCSI_DATA_OUT:
		data_out_end(conn, cmnd);
		break;
	case ISCSI_OP_SNACK_CMD:
		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", cmnd_opcode(cmnd));
		BUG();
		break;
	}
}

static void iscsi_exit(void)
{
	unregister_chrdev(ctr_major, ctr_name);

	iet_procfs_exit();

	event_exit();

	tio_exit();

	iotype_exit();

	if (iscsi_cmnd_cache)
		kmem_cache_destroy(iscsi_cmnd_cache);
}

static int iscsi_init(void)
{
	int err = -ENOMEM;

	printk("iSCSI Enterprise Target Software - version %s\n", IET_VERSION_STRING);

	if ((ctr_major = register_chrdev(0, ctr_name, &ctr_fops)) < 0) {
		eprintk("failed to register the control device %d\n", ctr_major);
		return ctr_major;
	}

	if ((err = iet_procfs_init()) < 0)
		goto err;

	if ((err = event_init()) < 0)
		goto err;

	iscsi_cmnd_cache = KMEM_CACHE(iscsi_cmnd, 0);
	if (!iscsi_cmnd_cache)
		goto err;

	if ((err = tio_init()) < 0)
		goto err;

	if ((err = iotype_init()) < 0)
		goto err;

	return 0;

err:
	iscsi_exit();
	return err;
}

module_param(debug_enable_flags, ulong, S_IRUGO);

module_init(iscsi_init);
module_exit(iscsi_exit);

MODULE_LICENSE("GPL");
