Skip to content
Snippets Groups Projects
iscsi_tcp.c 93.7 KiB
Newer Older
	mutex_lock(&conn->xmitmutex);
	spin_lock_bh(&session->lock);
	if (session->state != ISCSI_STATE_LOGGED_IN) {
		if (session->state == ISCSI_STATE_TERMINATE) {
			spin_unlock_bh(&session->lock);
			mutex_unlock(&conn->xmitmutex);
			goto failed;
		}
		spin_unlock_bh(&session->lock);
	} else {
		struct iscsi_tm *hdr = &conn->tmhdr;

		/*
		 * Still LOGGED_IN...
		 */

		if (!ctask->sc || sc->SCp.phase != session->age) {
			/*
			 * 1) ctask completed before time out. But session
			 *    is still ok => Happy Retry.
			 * 2) session was re-open during time out of ctask.
			 */
			spin_unlock_bh(&session->lock);
			mutex_unlock(&conn->xmitmutex);
			goto success;
		}
		conn->tmabort_state = TMABORT_INITIAL;
		spin_unlock_bh(&session->lock);

		/*
		 * ctask timed out but session is OK
		 * ERL=0 requires task mgmt abort to be issued on each
		 * failed command. requests must be serialized.
		 */
		memset(hdr, 0, sizeof(struct iscsi_tm));
		hdr->opcode = ISCSI_OP_SCSI_TMFUNC | ISCSI_OP_IMMEDIATE;
		hdr->flags = ISCSI_TM_FUNC_ABORT_TASK;
		hdr->flags |= ISCSI_FLAG_CMD_FINAL;
		memcpy(hdr->lun, ctask->hdr.lun, sizeof(hdr->lun));
		hdr->rtt = ctask->hdr.itt;
		hdr->refcmdsn = ctask->hdr.cmdsn;

		rc = iscsi_conn_send_generic(conn, (struct iscsi_hdr *)hdr,
					     NULL, 0);
		if (rc) {
			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
			debug_scsi("abort sent failure [itt 0x%x]", ctask->itt);
		} else {
			struct iscsi_r2t_info *r2t;

			/*
			 * TMF abort vs. TMF response race logic
			 */
			spin_lock_bh(&session->lock);
			ctask->mtask = (struct iscsi_mgmt_task *)
				session->mgmt_cmds[(hdr->itt & ITT_MASK) -
							ISCSI_MGMT_ITT_OFFSET];
			/*
			 * have to flush r2tqueue to avoid r2t leaks
			 */
			while (__kfifo_get(ctask->r2tqueue, (void*)&r2t,
				sizeof(void*))) {
				__kfifo_put(ctask->r2tpool.queue, (void*)&r2t,
					sizeof(void*));
			}
			if (conn->tmabort_state == TMABORT_INITIAL) {
				conn->tmfcmd_pdus_cnt++;
				conn->tmabort_timer.expires = 3*HZ + jiffies;
				conn->tmabort_timer.function =
						iscsi_tmabort_timedout;
				conn->tmabort_timer.data = (unsigned long)ctask;
				add_timer(&conn->tmabort_timer);
				debug_scsi("abort sent [itt 0x%x]", ctask->itt);
			} else {
				if (!ctask->sc ||
				    conn->tmabort_state == TMABORT_SUCCESS) {
					conn->tmabort_state = TMABORT_INITIAL;
					spin_unlock_bh(&session->lock);
					mutex_unlock(&conn->xmitmutex);
					goto success;
				}
				conn->tmabort_state = TMABORT_INITIAL;
				iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
			}
			spin_unlock_bh(&session->lock);
		}
	}
	mutex_unlock(&conn->xmitmutex);


	/*
	 * block eh thread until:
	 *
	 * 1) abort response;
	 * 2) abort timeout;
	 * 3) session re-opened;
	 * 4) session terminated;
	 */
	for (;;) {
		int p_state = session->state;

		rc = wait_event_interruptible(conn->ehwait,
			(p_state == ISCSI_STATE_LOGGED_IN ?
			 (session->state == ISCSI_STATE_TERMINATE ||
			  conn->tmabort_state != TMABORT_INITIAL) :
			 (session->state == ISCSI_STATE_TERMINATE ||
			  session->state == ISCSI_STATE_LOGGED_IN)));
		if (rc) {
			/* shutdown.. */
			session->state = ISCSI_STATE_TERMINATE;
			goto failed;
		}

		if (signal_pending(current))
			flush_signals(current);

		if (session->state == ISCSI_STATE_TERMINATE)
			goto failed;

		spin_lock_bh(&session->lock);
		if (sc->SCp.phase == session->age &&
		   (conn->tmabort_state == TMABORT_TIMEDOUT ||
		    conn->tmabort_state == TMABORT_FAILED)) {
			conn->tmabort_state = TMABORT_INITIAL;
			if (!ctask->sc) {
				/*
				 * ctask completed before tmf abort response or
				 * time out.
				 * But session is still ok => Happy Retry.
				 */
				spin_unlock_bh(&session->lock);
				break;
			}
			spin_unlock_bh(&session->lock);
			iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
			continue;
		}
		spin_unlock_bh(&session->lock);
		break;
	}

success:
	debug_scsi("abort success [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
	rc = SUCCESS;
	goto exit;

failed:
	debug_scsi("abort failed [sc %lx itt 0x%x]\n", (long)sc, ctask->itt);
	rc = FAILED;

exit:
	del_timer_sync(&conn->tmabort_timer);

	mutex_lock(&conn->xmitmutex);
	if (conn->sock) {
		struct sock *sk = conn->sock->sk;

		write_lock_bh(&sk->sk_callback_lock);
		iscsi_ctask_cleanup(conn, ctask);
		write_unlock_bh(&sk->sk_callback_lock);
	}
	mutex_unlock(&conn->xmitmutex);
	return rc;
}

static int
iscsi_r2tpool_alloc(struct iscsi_session *session)
{
	int i;
	int cmd_i;

	/*
	 * initialize per-task: R2T pool and xmit queue
	 */
	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
	        struct iscsi_cmd_task *ctask = session->cmds[cmd_i];

		/*
		 * pre-allocated x4 as much r2ts to handle race when
		 * target acks DataOut faster than we data_xmit() queues
		 * could replenish r2tqueue.
		 */

		/* R2T pool */
		if (iscsi_pool_init(&ctask->r2tpool, session->max_r2t * 4,
			(void***)&ctask->r2ts, sizeof(struct iscsi_r2t_info))) {
			goto r2t_alloc_fail;
		}

		/* R2T xmit queue */
		ctask->r2tqueue = kfifo_alloc(
		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
		if (ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
			iscsi_pool_free(&ctask->r2tpool, (void**)ctask->r2ts);
			goto r2t_alloc_fail;
		}

		/*
		 * number of
		 * Data-Out PDU's within R2T-sequence can be quite big;
		 * using mempool
		 */
		ctask->datapool = mempool_create(ISCSI_DTASK_DEFAULT_MAX,
			 mempool_alloc_slab, mempool_free_slab, taskcache);
		if (ctask->datapool == NULL) {
			kfifo_free(ctask->r2tqueue);
			iscsi_pool_free(&ctask->r2tpool, (void**)ctask->r2ts);
			goto r2t_alloc_fail;
		}
		INIT_LIST_HEAD(&ctask->dataqueue);
	}

	return 0;

r2t_alloc_fail:
	for (i = 0; i < cmd_i; i++) {
		mempool_destroy(session->cmds[i]->datapool);
		kfifo_free(session->cmds[i]->r2tqueue);
		iscsi_pool_free(&session->cmds[i]->r2tpool,
				(void**)session->cmds[i]->r2ts);
	}
	return -ENOMEM;
}

static void
iscsi_r2tpool_free(struct iscsi_session *session)
{
	int i;

	for (i = 0; i < session->cmds_max; i++) {
		mempool_destroy(session->cmds[i]->datapool);
		kfifo_free(session->cmds[i]->r2tqueue);
		iscsi_pool_free(&session->cmds[i]->r2tpool,
				(void**)session->cmds[i]->r2ts);
	}
}

static struct scsi_host_template iscsi_sht = {
	.name			= "iSCSI Initiator over TCP/IP, v."
				  ISCSI_VERSION_STR,
	.queuecommand           = iscsi_queuecommand,
	.change_queue_depth	= iscsi_change_queue_depth,
	.can_queue		= ISCSI_XMIT_CMDS_MAX - 1,
	.sg_tablesize		= ISCSI_SG_TABLESIZE,
	.cmd_per_lun		= ISCSI_DEF_CMD_PER_LUN,
	.eh_abort_handler       = iscsi_eh_abort,
	.eh_host_reset_handler	= iscsi_eh_host_reset,
	.use_clustering         = DISABLE_CLUSTERING,
	.proc_name		= "iscsi_tcp",
	.this_id		= -1,
};

static struct iscsi_transport iscsi_tcp_transport;

static struct Scsi_Host *
iscsi_session_create(struct scsi_transport_template *scsit,
		     uint32_t initial_cmdsn)
	struct iscsi_session *session;
	shost = iscsi_transport_create_session(scsit, &iscsi_tcp_transport);
	if (!shost)
		return NULL; 
	session = iscsi_hostdata(shost->hostdata);
	memset(session, 0, sizeof(struct iscsi_session));
	session->host = shost;
	session->state = ISCSI_STATE_LOGGED_IN;
	session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX;
	session->cmds_max = ISCSI_XMIT_CMDS_MAX;
	session->cmdsn = initial_cmdsn;
	session->exp_cmdsn = initial_cmdsn + 1;
	session->max_cmdsn = initial_cmdsn + 1;
	session->max_r2t = 1;

	/* initialize SCSI PDU commands pool */
	if (iscsi_pool_init(&session->cmdpool, session->cmds_max,
		(void***)&session->cmds, sizeof(struct iscsi_cmd_task)))
		goto cmdpool_alloc_fail;

	/* pre-format cmds pool with ITT */
	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++)
		session->cmds[cmd_i]->itt = cmd_i;

	spin_lock_init(&session->lock);
	INIT_LIST_HEAD(&session->connections);

	/* initialize immediate command pool */
	if (iscsi_pool_init(&session->mgmtpool, session->mgmtpool_max,
		(void***)&session->mgmt_cmds, sizeof(struct iscsi_mgmt_task)))
		goto mgmtpool_alloc_fail;


	/* pre-format immediate cmds pool with ITT */
	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
		session->mgmt_cmds[cmd_i]->itt = ISCSI_MGMT_ITT_OFFSET + cmd_i;
		session->mgmt_cmds[cmd_i]->data = kmalloc(
			DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH, GFP_KERNEL);
		if (!session->mgmt_cmds[cmd_i]->data) {
			int j;

			for (j = 0; j < cmd_i; j++)
				kfree(session->mgmt_cmds[j]->data);
			goto immdata_alloc_fail;
		}
	}

	if (iscsi_r2tpool_alloc(session))
		goto r2tpool_alloc_fail;


r2tpool_alloc_fail:
	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++)
		kfree(session->mgmt_cmds[cmd_i]->data);
	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
immdata_alloc_fail:
mgmtpool_alloc_fail:
	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);
cmdpool_alloc_fail:
iscsi_session_destroy(struct Scsi_Host *shost)
	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);
	int cmd_i;
	struct iscsi_data_task *dtask, *n;

	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
		list_for_each_entry_safe(dtask, n, &ctask->dataqueue, item) {
			list_del(&dtask->item);
			mempool_free(dtask, ctask->datapool);
		}
	}

	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++)
		kfree(session->mgmt_cmds[cmd_i]->data);

	iscsi_r2tpool_free(session);
	iscsi_pool_free(&session->mgmtpool, (void**)session->mgmt_cmds);
	iscsi_pool_free(&session->cmdpool, (void**)session->cmds);

	iscsi_transport_destroy_session(shost);
}

static int
iscsi_conn_set_param(iscsi_connh_t connh, enum iscsi_param param,
		     uint32_t value)
{
	struct iscsi_conn *conn = iscsi_ptr(connh);
	struct iscsi_session *session = conn->session;

	spin_lock_bh(&session->lock);
	if (conn->c_stage != ISCSI_CONN_INITIAL_STAGE &&
	    conn->stop_stage != STOP_CONN_RECOVER) {
		printk(KERN_ERR "iscsi_tcp: can not change parameter [%d]\n",
		       param);
		spin_unlock_bh(&session->lock);
		return 0;
	}
	spin_unlock_bh(&session->lock);

	switch(param) {
	case ISCSI_PARAM_MAX_RECV_DLENGTH: {
		char *saveptr = conn->data;
Al Viro's avatar
Al Viro committed
		gfp_t flags = GFP_KERNEL;

		if (conn->data_size >= value) {
			conn->max_recv_dlength = value;
			break;
		}

		spin_lock_bh(&session->lock);
		if (conn->stop_stage == STOP_CONN_RECOVER)
			flags = GFP_ATOMIC;
		spin_unlock_bh(&session->lock);

		if (value <= PAGE_SIZE)
			conn->data = kmalloc(value, flags);
		else
			conn->data = (void*)__get_free_pages(flags,
							     get_order(value));
		if (conn->data == NULL) {
			conn->data = saveptr;
			return -ENOMEM;
		}
		if (conn->data_size <= PAGE_SIZE)
			kfree(saveptr);
		else
			free_pages((unsigned long)saveptr,
				   get_order(conn->data_size));
		conn->max_recv_dlength = value;
		conn->data_size = value;
		}
		break;
	case ISCSI_PARAM_MAX_XMIT_DLENGTH:
		conn->max_xmit_dlength =  value;
		break;
	case ISCSI_PARAM_HDRDGST_EN:
		conn->hdrdgst_en = value;
		conn->hdr_size = sizeof(struct iscsi_hdr);
		if (conn->hdrdgst_en) {
			conn->hdr_size += sizeof(__u32);
			if (!conn->tx_tfm)
				conn->tx_tfm = crypto_alloc_tfm("crc32c", 0);
			if (!conn->tx_tfm)
				return -ENOMEM;
			if (!conn->rx_tfm)
				conn->rx_tfm = crypto_alloc_tfm("crc32c", 0);
			if (!conn->rx_tfm) {
				crypto_free_tfm(conn->tx_tfm);
				return -ENOMEM;
			}
		} else {
			if (conn->tx_tfm)
				crypto_free_tfm(conn->tx_tfm);
			if (conn->rx_tfm)
				crypto_free_tfm(conn->rx_tfm);
		}
		break;
	case ISCSI_PARAM_DATADGST_EN:
		conn->datadgst_en = value;
		if (conn->datadgst_en) {
			if (!conn->data_tx_tfm)
				conn->data_tx_tfm =
				    crypto_alloc_tfm("crc32c", 0);
			if (!conn->data_tx_tfm)
				return -ENOMEM;
			if (!conn->data_rx_tfm)
				conn->data_rx_tfm =
				    crypto_alloc_tfm("crc32c", 0);
			if (!conn->data_rx_tfm) {
				crypto_free_tfm(conn->data_tx_tfm);
				return -ENOMEM;
			}
		} else {
			if (conn->data_tx_tfm)
				crypto_free_tfm(conn->data_tx_tfm);
			if (conn->data_rx_tfm)
				crypto_free_tfm(conn->data_rx_tfm);
		}
		conn->sendpage = conn->datadgst_en ?
			sock_no_sendpage : conn->sock->ops->sendpage;
		break;
	case ISCSI_PARAM_INITIAL_R2T_EN:
		session->initial_r2t_en = value;
		break;
	case ISCSI_PARAM_MAX_R2T:
		if (session->max_r2t == roundup_pow_of_two(value))
			break;
		iscsi_r2tpool_free(session);
		session->max_r2t = value;
		if (session->max_r2t & (session->max_r2t - 1))
			session->max_r2t = roundup_pow_of_two(session->max_r2t);
		if (iscsi_r2tpool_alloc(session))
			return -ENOMEM;
		break;
	case ISCSI_PARAM_IMM_DATA_EN:
		session->imm_data_en = value;
		break;
	case ISCSI_PARAM_FIRST_BURST:
		session->first_burst = value;
		break;
	case ISCSI_PARAM_MAX_BURST:
		session->max_burst = value;
		break;
	case ISCSI_PARAM_PDU_INORDER_EN:
		session->pdu_inorder_en = value;
		break;
	case ISCSI_PARAM_DATASEQ_INORDER_EN:
		session->dataseq_inorder_en = value;
		break;
	case ISCSI_PARAM_ERL:
		session->erl = value;
		break;
	case ISCSI_PARAM_IFMARKER_EN:
		BUG_ON(value);
		session->ifmarker_en = value;
		break;
	case ISCSI_PARAM_OFMARKER_EN:
		BUG_ON(value);
		session->ofmarker_en = value;
		break;
	default:
		break;
	}

	return 0;
}

static int
iscsi_session_get_param(struct Scsi_Host *shost,
			enum iscsi_param param, uint32_t *value)
	struct iscsi_session *session = iscsi_hostdata(shost->hostdata);

	switch(param) {
	case ISCSI_PARAM_INITIAL_R2T_EN:
		*value = session->initial_r2t_en;
		break;
	case ISCSI_PARAM_MAX_R2T:
		*value = session->max_r2t;
		break;
	case ISCSI_PARAM_IMM_DATA_EN:
		*value = session->imm_data_en;
		break;
	case ISCSI_PARAM_FIRST_BURST:
		*value = session->first_burst;
		break;
	case ISCSI_PARAM_MAX_BURST:
		*value = session->max_burst;
		break;
	case ISCSI_PARAM_PDU_INORDER_EN:
		*value = session->pdu_inorder_en;
		break;
	case ISCSI_PARAM_DATASEQ_INORDER_EN:
		*value = session->dataseq_inorder_en;
		break;
	case ISCSI_PARAM_ERL:
		*value = session->erl;
		break;
	case ISCSI_PARAM_IFMARKER_EN:
		*value = session->ifmarker_en;
		break;
	case ISCSI_PARAM_OFMARKER_EN:
		*value = session->ofmarker_en;
		break;
	default:
		return ISCSI_ERR_PARAM_NOT_FOUND;
	}

	return 0;
}

static int
iscsi_conn_get_param(void *data, enum iscsi_param param, uint32_t *value)
{
	struct iscsi_conn *conn = data;

	switch(param) {
	case ISCSI_PARAM_MAX_RECV_DLENGTH:
		*value = conn->max_recv_dlength;
		break;
	case ISCSI_PARAM_MAX_XMIT_DLENGTH:
		*value = conn->max_xmit_dlength;
		break;
	case ISCSI_PARAM_HDRDGST_EN:
		*value = conn->hdrdgst_en;
		break;
	case ISCSI_PARAM_DATADGST_EN:
		*value = conn->datadgst_en;
		break;
	default:
		return ISCSI_ERR_PARAM_NOT_FOUND;
	}

	return 0;
}

static void
iscsi_conn_get_stats(iscsi_connh_t connh, struct iscsi_stats *stats)
{
	struct iscsi_conn *conn = iscsi_ptr(connh);

	stats->txdata_octets = conn->txdata_octets;
	stats->rxdata_octets = conn->rxdata_octets;
	stats->scsicmd_pdus = conn->scsicmd_pdus_cnt;
	stats->dataout_pdus = conn->dataout_pdus_cnt;
	stats->scsirsp_pdus = conn->scsirsp_pdus_cnt;
	stats->datain_pdus = conn->datain_pdus_cnt;
	stats->r2t_pdus = conn->r2t_pdus_cnt;
	stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
	stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
	stats->custom_length = 3;
	strcpy(stats->custom[0].desc, "tx_sendpage_failures");
	stats->custom[0].value = conn->sendpage_failures_cnt;
	strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
	stats->custom[1].value = conn->discontiguous_hdr_cnt;
	strcpy(stats->custom[2].desc, "eh_abort_cnt");
	stats->custom[2].value = conn->eh_abort_cnt;
}

static int
iscsi_conn_send_pdu(iscsi_connh_t connh, struct iscsi_hdr *hdr, char *data,
		    uint32_t data_size)
{
	struct iscsi_conn *conn = iscsi_ptr(connh);
	int rc;

	mutex_lock(&conn->xmitmutex);
	rc = iscsi_conn_send_generic(conn, hdr, data, data_size);
	mutex_unlock(&conn->xmitmutex);

	return rc;
}

static struct iscsi_transport iscsi_tcp_transport = {
	.owner			= THIS_MODULE,
	.name			= "tcp",
	.caps			= CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
				  | CAP_DATADGST,
	.host_template		= &iscsi_sht,
	.hostdata_size		= sizeof(struct iscsi_session),
	.conndata_size		= sizeof(struct iscsi_conn),
	.max_conn		= 1,
	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
	.create_session		= iscsi_session_create,
	.destroy_session	= iscsi_session_destroy,
	.create_conn		= iscsi_conn_create,
	.bind_conn		= iscsi_conn_bind,
	.destroy_conn		= iscsi_conn_destroy,
	.set_param		= iscsi_conn_set_param,
	.get_conn_param		= iscsi_conn_get_param,
	.get_session_param	= iscsi_session_get_param,
	.start_conn		= iscsi_conn_start,
	.stop_conn		= iscsi_conn_stop,
	.send_pdu		= iscsi_conn_send_pdu,
	.get_stats		= iscsi_conn_get_stats,
};

static int __init
iscsi_tcp_init(void)
{
	if (iscsi_max_lun < 1) {
		printk(KERN_ERR "Invalid max_lun value of %u\n", iscsi_max_lun);
		return -EINVAL;
	}
	iscsi_tcp_transport.max_lun = iscsi_max_lun;

	taskcache = kmem_cache_create("iscsi_taskcache",
			sizeof(struct iscsi_data_task), 0,
			SLAB_HWCACHE_ALIGN | SLAB_NO_REAP, NULL, NULL);
	if (!taskcache)
		return -ENOMEM;

	if (!iscsi_register_transport(&iscsi_tcp_transport))
		kmem_cache_destroy(taskcache);

}

static void __exit
iscsi_tcp_exit(void)
{
	iscsi_unregister_transport(&iscsi_tcp_transport);
	kmem_cache_destroy(taskcache);
}

module_init(iscsi_tcp_init);
module_exit(iscsi_tcp_exit);