Skip to content
Snippets Groups Projects
iscsi_tcp.c 68.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    		/* setup Socket parameters */
    		sk = sock->sk;
    		sk->sk_reuse = 1;
    		sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */
    		sk->sk_allocation = GFP_ATOMIC;
    
    		/* FIXME: disable Nagle's algorithm */
    
    		/*
    		 * Intercept TCP callbacks for sendfile like receive
    		 * processing.
    		 */
    
    		conn->recv_lock = &sk->sk_callback_lock;
    
    		iscsi_conn_set_callbacks(conn);
    
    		tcp_conn->sendpage = tcp_conn->sock->ops->sendpage;
    
    		/*
    		 * set receive state machine into initial state
    		 */
    
    		tcp_conn->in_progress = IN_PROGRESS_WAIT_HEADER;
    
    static void
    
    iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask)
    
    	struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    	struct iscsi_r2t_info *r2t;
    
    	/* flush ctask's r2t queues */
    
    	while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*)))
    		__kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t,
    
    			    sizeof(void*));
    
    
    	__iscsi_ctask_cleanup(conn, ctask);
    
    iscsi_tcp_suspend_conn_rx(struct iscsi_conn *conn)
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    	if (!tcp_conn->sock)
    		return;
    
    	sk = tcp_conn->sock->sk;
    
    	write_lock_bh(&sk->sk_callback_lock);
    
    	set_bit(ISCSI_SUSPEND_BIT, &conn->suspend_rx);
    
    	write_unlock_bh(&sk->sk_callback_lock);
    
    static void
    
    iscsi_tcp_terminate_conn(struct iscsi_conn *conn)
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    	if (!tcp_conn->sock)
    		return;
    
    	sock_hold(tcp_conn->sock->sk);
    
    	iscsi_conn_restore_callbacks(conn);
    
    	sock_put(tcp_conn->sock->sk);
    
    	sock_release(tcp_conn->sock);
    	tcp_conn->sock = NULL;
    	conn->recv_lock = NULL;
    
    /* called with host lock */
    
    static void
    
    iscsi_tcp_mgmt_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask,
    		    char *data, uint32_t data_size)
    
    	struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
    
    	iscsi_buf_init_virt(&tcp_mtask->headbuf, (char*)mtask->hdr,
    
    				    sizeof(struct iscsi_hdr));
    
    	tcp_mtask->xmstate = XMSTATE_IMM_HDR;
    
    	if (mtask->data_count)
    		iscsi_buf_init_iov(&tcp_mtask->sendbuf, (char*)mtask->data,
    
    				    mtask->data_count);
    }
    
    static int
    iscsi_r2tpool_alloc(struct iscsi_session *session)
    {
    	int i;
    	int cmd_i;
    
    	/*
    	 * initialize per-task: R2T pool and xmit queue
    	 */
    	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
    	        struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
    
    		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    
    		/*
    		 * pre-allocated x4 as much r2ts to handle race when
    		 * target acks DataOut faster than we data_xmit() queues
    		 * could replenish r2tqueue.
    		 */
    
    		/* R2T pool */
    
    		if (iscsi_pool_init(&tcp_ctask->r2tpool, session->max_r2t * 4,
    				    (void***)&tcp_ctask->r2ts,
    				    sizeof(struct iscsi_r2t_info))) {
    
    			goto r2t_alloc_fail;
    		}
    
    		/* R2T xmit queue */
    
    		tcp_ctask->r2tqueue = kfifo_alloc(
    
    		      session->max_r2t * 4 * sizeof(void*), GFP_KERNEL, NULL);
    
    		if (tcp_ctask->r2tqueue == ERR_PTR(-ENOMEM)) {
    			iscsi_pool_free(&tcp_ctask->r2tpool,
    					(void**)tcp_ctask->r2ts);
    
    			goto r2t_alloc_fail;
    		}
    
    		/*
    		 * number of
    		 * Data-Out PDU's within R2T-sequence can be quite big;
    		 * using mempool
    		 */
    
    		tcp_ctask->datapool = mempool_create_slab_pool(ISCSI_DTASK_DEFAULT_MAX,
    							       taskcache);
    		if (tcp_ctask->datapool == NULL) {
    			kfifo_free(tcp_ctask->r2tqueue);
    			iscsi_pool_free(&tcp_ctask->r2tpool,
    					(void**)tcp_ctask->r2ts);
    
    		INIT_LIST_HEAD(&tcp_ctask->dataqueue);
    
    	}
    
    	return 0;
    
    r2t_alloc_fail:
    	for (i = 0; i < cmd_i; i++) {
    
    		struct iscsi_cmd_task *ctask = session->cmds[i];
    		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    		mempool_destroy(tcp_ctask->datapool);
    		kfifo_free(tcp_ctask->r2tqueue);
    		iscsi_pool_free(&tcp_ctask->r2tpool,
    				(void**)tcp_ctask->r2ts);
    
    	}
    	return -ENOMEM;
    }
    
    static void
    iscsi_r2tpool_free(struct iscsi_session *session)
    {
    	int i;
    
    	for (i = 0; i < session->cmds_max; i++) {
    
    		struct iscsi_cmd_task *ctask = session->cmds[i];
    		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    		mempool_destroy(tcp_ctask->datapool);
    		kfifo_free(tcp_ctask->r2tqueue);
    		iscsi_pool_free(&tcp_ctask->r2tpool,
    				(void**)tcp_ctask->r2ts);
    
    iscsi_conn_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param,
    
    	struct iscsi_conn *conn = cls_conn->dd_data;
    
    	struct iscsi_session *session = conn->session;
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    
    	switch(param) {
    	case ISCSI_PARAM_MAX_RECV_DLENGTH: {
    
    		char *saveptr = tcp_conn->data;
    
    Al Viro's avatar
    Al Viro committed
    		gfp_t flags = GFP_KERNEL;
    
    		if (tcp_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)
    
    			tcp_conn->data = kmalloc(value, flags);
    
    			tcp_conn->data = (void*)__get_free_pages(flags,
    
    		if (tcp_conn->data == NULL) {
    			tcp_conn->data = saveptr;
    
    		if (tcp_conn->data_size <= PAGE_SIZE)
    
    			kfree(saveptr);
    		else
    			free_pages((unsigned long)saveptr,
    
    				   get_order(tcp_conn->data_size));
    
    		conn->max_recv_dlength = value;
    
    		tcp_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;
    
    		tcp_conn->hdr_size = sizeof(struct iscsi_hdr);
    
    			tcp_conn->hdr_size += sizeof(__u32);
    			if (!tcp_conn->tx_tfm)
    				tcp_conn->tx_tfm = crypto_alloc_tfm("crc32c",
    								    0);
    			if (!tcp_conn->tx_tfm)
    
    			if (!tcp_conn->rx_tfm)
    				tcp_conn->rx_tfm = crypto_alloc_tfm("crc32c",
    								    0);
    			if (!tcp_conn->rx_tfm) {
    				crypto_free_tfm(tcp_conn->tx_tfm);
    
    			if (tcp_conn->tx_tfm)
    				crypto_free_tfm(tcp_conn->tx_tfm);
    			if (tcp_conn->rx_tfm)
    				crypto_free_tfm(tcp_conn->rx_tfm);
    
    		}
    		break;
    	case ISCSI_PARAM_DATADGST_EN:
    		conn->datadgst_en = value;
    		if (conn->datadgst_en) {
    
    			if (!tcp_conn->data_tx_tfm)
    				tcp_conn->data_tx_tfm =
    
    				    crypto_alloc_tfm("crc32c", 0);
    
    			if (!tcp_conn->data_tx_tfm)
    
    			if (!tcp_conn->data_rx_tfm)
    				tcp_conn->data_rx_tfm =
    
    				    crypto_alloc_tfm("crc32c", 0);
    
    			if (!tcp_conn->data_rx_tfm) {
    				crypto_free_tfm(tcp_conn->data_tx_tfm);
    
    			if (tcp_conn->data_tx_tfm)
    				crypto_free_tfm(tcp_conn->data_tx_tfm);
    			if (tcp_conn->data_rx_tfm)
    				crypto_free_tfm(tcp_conn->data_rx_tfm);
    
    		tcp_conn->sendpage = conn->datadgst_en ?
    			sock_no_sendpage : tcp_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 iscsi_cls_session *cls_session,
    
    			enum iscsi_param param, uint32_t *value)
    
    	struct Scsi_Host *shost = iscsi_session_to_shost(cls_session);
    
    	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:
    
    iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn,
    		     enum iscsi_param param, uint32_t *value)
    
    	struct iscsi_conn *conn = cls_conn->dd_data;
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    	struct inet_sock *inet;
    
    
    	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;
    
    	case ISCSI_PARAM_CONN_PORT:
    		mutex_lock(&conn->xmitmutex);
    
    		if (!tcp_conn->sock) {
    
    			mutex_unlock(&conn->xmitmutex);
    			return -EINVAL;
    		}
    
    
    		inet = inet_sk(tcp_conn->sock->sk);
    
    		*value = be16_to_cpu(inet->dport);
    		mutex_unlock(&conn->xmitmutex);
    
    static int
    iscsi_conn_get_str_param(struct iscsi_cls_conn *cls_conn,
    			 enum iscsi_param param, char *buf)
    {
    	struct iscsi_conn *conn = cls_conn->dd_data;
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    	struct sock *sk;
    	struct inet_sock *inet;
    	struct ipv6_pinfo *np;
    	int len = 0;
    
    	switch (param) {
    	case ISCSI_PARAM_CONN_ADDRESS:
    		mutex_lock(&conn->xmitmutex);
    
    		if (!tcp_conn->sock) {
    
    			mutex_unlock(&conn->xmitmutex);
    			return -EINVAL;
    		}
    
    
    		sk = tcp_conn->sock->sk;
    
    		if (sk->sk_family == PF_INET) {
    			inet = inet_sk(sk);
    			len = sprintf(buf, "%u.%u.%u.%u\n",
    				      NIPQUAD(inet->daddr));
    		} else {
    			np = inet6_sk(sk);
    			len = sprintf(buf,
    				"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
    				NIP6(np->daddr));
    		}
    		mutex_unlock(&conn->xmitmutex);
    		break;
    	default:
    		return -EINVAL;
    	}
    
    	return len;
    }
    
    
    iscsi_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *stats)
    
    	struct iscsi_conn *conn = cls_conn->dd_data;
    
    	struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
    
    
    	stats->txdata_octets = conn->txdata_octets;
    	stats->rxdata_octets = conn->rxdata_octets;
    	stats->scsicmd_pdus = conn->scsicmd_pdus_cnt;
    	stats->dataout_pdus = conn->dataout_pdus_cnt;
    	stats->scsirsp_pdus = conn->scsirsp_pdus_cnt;
    	stats->datain_pdus = conn->datain_pdus_cnt;
    	stats->r2t_pdus = conn->r2t_pdus_cnt;
    	stats->tmfcmd_pdus = conn->tmfcmd_pdus_cnt;
    	stats->tmfrsp_pdus = conn->tmfrsp_pdus_cnt;
    	stats->custom_length = 3;
    	strcpy(stats->custom[0].desc, "tx_sendpage_failures");
    
    	stats->custom[0].value = tcp_conn->sendpage_failures_cnt;
    
    	strcpy(stats->custom[1].desc, "rx_discontiguous_hdr");
    
    	stats->custom[1].value = tcp_conn->discontiguous_hdr_cnt;
    
    	strcpy(stats->custom[2].desc, "eh_abort_cnt");
    	stats->custom[2].value = conn->eh_abort_cnt;
    }
    
    
    static struct iscsi_cls_session *
    iscsi_tcp_session_create(struct iscsi_transport *iscsit,
    			 struct scsi_transport_template *scsit,
    			 uint32_t initial_cmdsn, uint32_t *hostno)
    
    	struct iscsi_cls_session *cls_session;
    	struct iscsi_session *session;
    	uint32_t hn;
    	int cmd_i;
    
    	cls_session = iscsi_session_setup(iscsit, scsit,
    					 sizeof(struct iscsi_tcp_cmd_task),
    					 sizeof(struct iscsi_tcp_mgmt_task),
    					 initial_cmdsn, &hn);
    	if (!cls_session)
    		return NULL;
    	*hostno = hn;
    
    	session = class_to_transport_session(cls_session);
    	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
    		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
    		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    		ctask->hdr = &tcp_ctask->hdr;
    	}
    
    	for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) {
    		struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i];
    		struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data;
    
    		mtask->hdr = &tcp_mtask->hdr;
    	}
    
    	if (iscsi_r2tpool_alloc(class_to_transport_session(cls_session)))
    		goto r2tpool_alloc_fail;
    
    	return cls_session;
    
    r2tpool_alloc_fail:
    	iscsi_session_teardown(cls_session);
    	return NULL;
    }
    
    static void iscsi_tcp_session_destroy(struct iscsi_cls_session *cls_session)
    {
    	struct iscsi_session *session = class_to_transport_session(cls_session);
    	struct iscsi_data_task *dtask, *n;
    	int cmd_i;
    
    	for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) {
    		struct iscsi_cmd_task *ctask = session->cmds[cmd_i];
    		struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data;
    
    		list_for_each_entry_safe(dtask, n, &tcp_ctask->dataqueue,
    					 item) {
    			list_del(&dtask->item);
    			mempool_free(dtask, tcp_ctask->datapool);
    		}
    	}
    
    	iscsi_r2tpool_free(class_to_transport_session(cls_session));
    	iscsi_session_teardown(cls_session);
    
    static struct scsi_host_template iscsi_sht = {
    	.name			= "iSCSI Initiator over TCP/IP, 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 = {
    	.owner			= THIS_MODULE,
    	.name			= "tcp",
    	.caps			= CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
    				  | CAP_DATADGST,
    
    	.param_mask		= ISCSI_MAX_RECV_DLENGTH |
    				  ISCSI_MAX_XMIT_DLENGTH |
    				  ISCSI_HDRDGST_EN |
    				  ISCSI_DATADGST_EN |
    				  ISCSI_INITIAL_R2T_EN |
    				  ISCSI_MAX_R2T |
    				  ISCSI_IMM_DATA_EN |
    				  ISCSI_FIRST_BURST |
    				  ISCSI_MAX_BURST |
    				  ISCSI_PDU_INORDER_EN |
    				  ISCSI_DATASEQ_INORDER_EN |
    				  ISCSI_ERL |
    				  ISCSI_CONN_PORT |
    				  ISCSI_CONN_ADDRESS,
    
    	.host_template		= &iscsi_sht,
    
    	.conndata_size		= sizeof(struct iscsi_conn),
    
    	.max_conn		= 1,
    	.max_cmd_len		= ISCSI_TCP_MAX_CMD_LEN,
    
    	/* session management */
    	.create_session		= iscsi_tcp_session_create,
    	.destroy_session	= iscsi_tcp_session_destroy,
    	/* connection management */
    	.create_conn		= iscsi_tcp_conn_create,
    	.bind_conn		= iscsi_tcp_conn_bind,
    	.destroy_conn		= iscsi_tcp_conn_destroy,
    
    	.set_param		= iscsi_conn_set_param,
    
    	.get_conn_param		= iscsi_conn_get_param,
    
    	.get_conn_str_param	= iscsi_conn_get_str_param,
    
    	.get_session_param	= iscsi_session_get_param,
    
    	.start_conn		= iscsi_conn_start,
    	.stop_conn		= iscsi_conn_stop,
    
    	/* these are called as part of conn recovery */
    	.suspend_conn_recv	= iscsi_tcp_suspend_conn_rx,
    	.terminate_conn		= iscsi_tcp_terminate_conn,
    	/* IO */
    
    	.send_pdu		= iscsi_conn_send_pdu,
    	.get_stats		= iscsi_conn_get_stats,
    
    	.init_cmd_task		= iscsi_tcp_cmd_init,
    	.init_mgmt_task		= iscsi_tcp_mgmt_init,
    	.xmit_cmd_task		= iscsi_tcp_ctask_xmit,
    	.xmit_mgmt_task		= iscsi_tcp_mtask_xmit,
    	.cleanup_cmd_task	= iscsi_tcp_cleanup_ctask,
    	/* recovery */
    
    	.session_recovery_timedout = iscsi_session_recovery_timedout,
    
    };
    
    static int __init
    iscsi_tcp_init(void)
    {
    	if (iscsi_max_lun < 1) {
    
    Or Gerlitz's avatar
    Or Gerlitz committed
    		printk(KERN_ERR "iscsi_tcp: Invalid max_lun value of %u\n",
    		       iscsi_max_lun);
    
    		return -EINVAL;
    	}
    	iscsi_tcp_transport.max_lun = iscsi_max_lun;
    
    	taskcache = kmem_cache_create("iscsi_taskcache",
    			sizeof(struct iscsi_data_task), 0,
    
    			SLAB_HWCACHE_ALIGN, NULL, NULL);
    
    	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);