3937 lines · 105.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * iSCSI lib functions * * Copyright (C) 2006 Red Hat, Inc.  All rights reserved. * Copyright (C) 2004 - 2006 Mike Christie * Copyright (C) 2004 - 2005 Dmitry Yusupov * Copyright (C) 2004 - 2005 Alex Aizman * maintained by open-iscsi@googlegroups.com */#include <linux/types.h>#include <linux/kfifo.h>#include <linux/delay.h>#include <linux/log2.h>#include <linux/slab.h>#include <linux/sched/signal.h>#include <linux/module.h>#include <linux/unaligned.h>#include <net/tcp.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_device.h>#include <scsi/scsi_eh.h>#include <scsi/scsi_tcq.h>#include <scsi/scsi_host.h>#include <scsi/scsi.h>#include <scsi/iscsi_proto.h>#include <scsi/scsi_transport.h>#include <scsi/scsi_transport_iscsi.h>#include <scsi/libiscsi.h>#include <trace/events/iscsi.h> static int iscsi_dbg_lib_conn;module_param_named(debug_libiscsi_conn, iscsi_dbg_lib_conn, int,		   S_IRUGO | S_IWUSR);MODULE_PARM_DESC(debug_libiscsi_conn,		 "Turn on debugging for connections in libiscsi module. "		 "Set to 1 to turn on, and zero to turn off. Default is off."); static int iscsi_dbg_lib_session;module_param_named(debug_libiscsi_session, iscsi_dbg_lib_session, int,		   S_IRUGO | S_IWUSR);MODULE_PARM_DESC(debug_libiscsi_session,		 "Turn on debugging for sessions in libiscsi module. "		 "Set to 1 to turn on, and zero to turn off. Default is off."); static int iscsi_dbg_lib_eh;module_param_named(debug_libiscsi_eh, iscsi_dbg_lib_eh, int,		   S_IRUGO | S_IWUSR);MODULE_PARM_DESC(debug_libiscsi_eh,		 "Turn on debugging for error handling in libiscsi module. "		 "Set to 1 to turn on, and zero to turn off. Default is off."); #define ISCSI_DBG_CONN(_conn, dbg_fmt, arg...)			\	do {							\		if (iscsi_dbg_lib_conn)				\			iscsi_conn_printk(KERN_INFO, _conn,	\					     "%s " dbg_fmt,	\					     __func__, ##arg);	\		iscsi_dbg_trace(trace_iscsi_dbg_conn,		\				&(_conn)->cls_conn->dev,	\				"%s " dbg_fmt, __func__, ##arg);\	} while (0); #define ISCSI_DBG_SESSION(_session, dbg_fmt, arg...)			\	do {								\		if (iscsi_dbg_lib_session)				\			iscsi_session_printk(KERN_INFO, _session,	\					     "%s " dbg_fmt,		\					     __func__, ##arg);		\		iscsi_dbg_trace(trace_iscsi_dbg_session, 		\				&(_session)->cls_session->dev,		\				"%s " dbg_fmt, __func__, ##arg);	\	} while (0); #define ISCSI_DBG_EH(_session, dbg_fmt, arg...)				\	do {								\		if (iscsi_dbg_lib_eh)					\			iscsi_session_printk(KERN_INFO, _session,	\					     "%s " dbg_fmt,		\					     __func__, ##arg);		\		iscsi_dbg_trace(trace_iscsi_dbg_eh,			\				&(_session)->cls_session->dev,		\				"%s " dbg_fmt, __func__, ##arg);	\	} while (0); #define ISCSI_CMD_COMPL_WAIT 5 inline void iscsi_conn_queue_xmit(struct iscsi_conn *conn){	struct Scsi_Host *shost = conn->session->host;	struct iscsi_host *ihost = shost_priv(shost); 	if (ihost->workq)		queue_work(ihost->workq, &conn->xmitwork);}EXPORT_SYMBOL_GPL(iscsi_conn_queue_xmit); inline void iscsi_conn_queue_recv(struct iscsi_conn *conn){	struct Scsi_Host *shost = conn->session->host;	struct iscsi_host *ihost = shost_priv(shost); 	if (ihost->workq && !test_bit(ISCSI_CONN_FLAG_SUSPEND_RX, &conn->flags))		queue_work(ihost->workq, &conn->recvwork);}EXPORT_SYMBOL_GPL(iscsi_conn_queue_recv); static void __iscsi_update_cmdsn(struct iscsi_session *session,				 uint32_t exp_cmdsn, uint32_t max_cmdsn){	/*	 * standard specifies this check for when to update expected and	 * max sequence numbers	 */	if (iscsi_sna_lt(max_cmdsn, exp_cmdsn - 1))		return; 	if (exp_cmdsn != session->exp_cmdsn &&	    !iscsi_sna_lt(exp_cmdsn, session->exp_cmdsn))		session->exp_cmdsn = exp_cmdsn; 	if (max_cmdsn != session->max_cmdsn &&	    !iscsi_sna_lt(max_cmdsn, session->max_cmdsn))		session->max_cmdsn = max_cmdsn;} void iscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr){	__iscsi_update_cmdsn(session, be32_to_cpu(hdr->exp_cmdsn),			     be32_to_cpu(hdr->max_cmdsn));}EXPORT_SYMBOL_GPL(iscsi_update_cmdsn); /** * iscsi_prep_data_out_pdu - initialize Data-Out * @task: scsi command task * @r2t: R2T info * @hdr: iscsi data in pdu * * Notes: *	Initialize Data-Out within this R2T sequence and finds *	proper data_offset within this SCSI command. * *	This function is called with connection lock taken. **/void iscsi_prep_data_out_pdu(struct iscsi_task *task, struct iscsi_r2t_info *r2t,			   struct iscsi_data *hdr){	struct iscsi_conn *conn = task->conn;	unsigned int left = r2t->data_length - r2t->sent; 	task->hdr_len = sizeof(struct iscsi_data); 	memset(hdr, 0, sizeof(struct iscsi_data));	hdr->ttt = r2t->ttt;	hdr->datasn = cpu_to_be32(r2t->datasn);	r2t->datasn++;	hdr->opcode = ISCSI_OP_SCSI_DATA_OUT;	hdr->lun = task->lun;	hdr->itt = task->hdr_itt;	hdr->exp_statsn = r2t->exp_statsn;	hdr->offset = cpu_to_be32(r2t->data_offset + r2t->sent);	if (left > conn->max_xmit_dlength) {		hton24(hdr->dlength, conn->max_xmit_dlength);		r2t->data_count = conn->max_xmit_dlength;		hdr->flags = 0;	} else {		hton24(hdr->dlength, left);		r2t->data_count = left;		hdr->flags = ISCSI_FLAG_CMD_FINAL;	}	conn->dataout_pdus_cnt++;}EXPORT_SYMBOL_GPL(iscsi_prep_data_out_pdu); static int iscsi_add_hdr(struct iscsi_task *task, unsigned len){	unsigned exp_len = task->hdr_len + len; 	if (exp_len > task->hdr_max) {		WARN_ON(1);		return -EINVAL;	} 	WARN_ON(len & (ISCSI_PAD_LEN - 1)); /* caller must pad the AHS */	task->hdr_len = exp_len;	return 0;} /* * make an extended cdb AHS */static int iscsi_prep_ecdb_ahs(struct iscsi_task *task){	struct scsi_cmnd *cmd = task->sc;	unsigned rlen, pad_len;	unsigned short ahslength;	struct iscsi_ecdb_ahdr *ecdb_ahdr;	int rc; 	ecdb_ahdr = iscsi_next_hdr(task);	rlen = cmd->cmd_len - ISCSI_CDB_SIZE; 	BUG_ON(rlen > sizeof(ecdb_ahdr->ecdb));	ahslength = rlen + sizeof(ecdb_ahdr->reserved); 	pad_len = iscsi_padding(rlen); 	rc = iscsi_add_hdr(task, sizeof(ecdb_ahdr->ahslength) +	                   sizeof(ecdb_ahdr->ahstype) + ahslength + pad_len);	if (rc)		return rc; 	if (pad_len)		memset(&ecdb_ahdr->ecdb[rlen], 0, pad_len); 	ecdb_ahdr->ahslength = cpu_to_be16(ahslength);	ecdb_ahdr->ahstype = ISCSI_AHSTYPE_CDB;	ecdb_ahdr->reserved = 0;	memcpy(ecdb_ahdr->ecdb, cmd->cmnd + ISCSI_CDB_SIZE, rlen); 	ISCSI_DBG_SESSION(task->conn->session,			  "iscsi_prep_ecdb_ahs: varlen_cdb_len %d "		          "rlen %d pad_len %d ahs_length %d iscsi_headers_size "		          "%u\n", cmd->cmd_len, rlen, pad_len, ahslength,		          task->hdr_len);	return 0;} /** * iscsi_check_tmf_restrictions - check if a task is affected by TMF * @task: iscsi task * @opcode: opcode to check for * * During TMF a task has to be checked if it's affected. * All unrelated I/O can be passed through, but I/O to the * affected LUN should be restricted. * If 'fast_abort' is set we won't be sending any I/O to the * affected LUN. * Otherwise the target is waiting for all TTTs to be completed, * so we have to send all outstanding Data-Out PDUs to the target. */static int iscsi_check_tmf_restrictions(struct iscsi_task *task, int opcode){	struct iscsi_session *session = task->conn->session;	struct iscsi_tm *tmf = &session->tmhdr;	u64 hdr_lun; 	if (session->tmf_state == TMF_INITIAL)		return 0; 	if ((tmf->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_SCSI_TMFUNC)		return 0; 	switch (ISCSI_TM_FUNC_VALUE(tmf)) {	case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:		/*		 * Allow PDUs for unrelated LUNs		 */		hdr_lun = scsilun_to_int(&tmf->lun);		if (hdr_lun != task->sc->device->lun)			return 0;		fallthrough;	case ISCSI_TM_FUNC_TARGET_WARM_RESET:		/*		 * Fail all SCSI cmd PDUs		 */		if (opcode != ISCSI_OP_SCSI_DATA_OUT) {			iscsi_session_printk(KERN_INFO, session,					     "task [op %x itt 0x%x/0x%x] rejected.\n",					     opcode, task->itt, task->hdr_itt);			return -EACCES;		}		/*		 * And also all data-out PDUs in response to R2T		 * if fast_abort is set.		 */		if (session->fast_abort) {			iscsi_session_printk(KERN_INFO, session,					     "task [op %x itt 0x%x/0x%x] fast abort.\n",					     opcode, task->itt, task->hdr_itt);			return -EACCES;		}		break;	case ISCSI_TM_FUNC_ABORT_TASK:		/*		 * the caller has already checked if the task		 * they want to abort was in the pending queue so if		 * we are here the cmd pdu has gone out already, and		 * we will only hit this for data-outs		 */		if (opcode == ISCSI_OP_SCSI_DATA_OUT &&		    task->hdr_itt == tmf->rtt) {			ISCSI_DBG_SESSION(session,					  "Preventing task %x/%x from sending "					  "data-out due to abort task in "					  "progress\n", task->itt,					  task->hdr_itt);			return -EACCES;		}		break;	} 	return 0;} /** * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu * @task: iscsi task * * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set * fields like dlength or final based on how much data it sends */static int iscsi_prep_scsi_cmd_pdu(struct iscsi_task *task){	struct iscsi_conn *conn = task->conn;	struct iscsi_session *session = conn->session;	struct scsi_cmnd *sc = task->sc;	struct iscsi_scsi_req *hdr;	unsigned hdrlength, cmd_len, transfer_length;	itt_t itt;	int rc; 	rc = iscsi_check_tmf_restrictions(task, ISCSI_OP_SCSI_CMD);	if (rc)		return rc; 	if (conn->session->tt->alloc_pdu) {		rc = conn->session->tt->alloc_pdu(task, ISCSI_OP_SCSI_CMD);		if (rc)			return rc;	}	hdr = (struct iscsi_scsi_req *)task->hdr;	itt = hdr->itt;	memset(hdr, 0, sizeof(*hdr)); 	if (session->tt->parse_pdu_itt)		hdr->itt = task->hdr_itt = itt;	else		hdr->itt = task->hdr_itt = build_itt(task->itt,						     task->conn->session->age);	task->hdr_len = 0;	rc = iscsi_add_hdr(task, sizeof(*hdr));	if (rc)		return rc;	hdr->opcode = ISCSI_OP_SCSI_CMD;	hdr->flags = ISCSI_ATTR_SIMPLE;	int_to_scsilun(sc->device->lun, &hdr->lun);	task->lun = hdr->lun;	hdr->exp_statsn = cpu_to_be32(conn->exp_statsn);	cmd_len = sc->cmd_len;	if (cmd_len < ISCSI_CDB_SIZE)		memset(&hdr->cdb[cmd_len], 0, ISCSI_CDB_SIZE - cmd_len);	else if (cmd_len > ISCSI_CDB_SIZE) {		rc = iscsi_prep_ecdb_ahs(task);		if (rc)			return rc;		cmd_len = ISCSI_CDB_SIZE;	}	memcpy(hdr->cdb, sc->cmnd, cmd_len); 	task->imm_count = 0;	if (scsi_get_prot_op(sc) != SCSI_PROT_NORMAL)		task->protected = true; 	transfer_length = scsi_transfer_length(sc);	hdr->data_length = cpu_to_be32(transfer_length);	if (sc->sc_data_direction == DMA_TO_DEVICE) {		struct iscsi_r2t_info *r2t = &task->unsol_r2t; 		hdr->flags |= ISCSI_FLAG_CMD_WRITE;		/*		 * Write counters:		 *		 *	imm_count	bytes to be sent right after		 *			SCSI PDU Header		 *		 *	unsol_count	bytes(as Data-Out) to be sent		 *			without	R2T ack right after		 *			immediate data		 *		 *	r2t data_length bytes to be sent via R2T ack's		 *		 *      pad_count       bytes to be sent as zero-padding		 */		memset(r2t, 0, sizeof(*r2t)); 		if (session->imm_data_en) {			if (transfer_length >= session->first_burst)				task->imm_count = min(session->first_burst,							conn->max_xmit_dlength);			else				task->imm_count = min(transfer_length,						      conn->max_xmit_dlength);			hton24(hdr->dlength, task->imm_count);		} else			zero_data(hdr->dlength); 		if (!session->initial_r2t_en) {			r2t->data_length = min(session->first_burst,					       transfer_length) -					       task->imm_count;			r2t->data_offset = task->imm_count;			r2t->ttt = cpu_to_be32(ISCSI_RESERVED_TAG);			r2t->exp_statsn = cpu_to_be32(conn->exp_statsn);		} 		if (!task->unsol_r2t.data_length)			/* No unsolicit Data-Out's */			hdr->flags |= ISCSI_FLAG_CMD_FINAL;	} else {		hdr->flags |= ISCSI_FLAG_CMD_FINAL;		zero_data(hdr->dlength); 		if (sc->sc_data_direction == DMA_FROM_DEVICE)			hdr->flags |= ISCSI_FLAG_CMD_READ;	} 	/* calculate size of additional header segments (AHSs) */	hdrlength = task->hdr_len - sizeof(*hdr); 	WARN_ON(hdrlength & (ISCSI_PAD_LEN-1));	hdrlength /= ISCSI_PAD_LEN; 	WARN_ON(hdrlength >= 256);	hdr->hlength = hdrlength & 0xFF;	hdr->cmdsn = task->cmdsn = cpu_to_be32(session->cmdsn); 	if (session->tt->init_task && session->tt->init_task(task))		return -EIO; 	task->state = ISCSI_TASK_RUNNING;	session->cmdsn++; 	conn->scsicmd_pdus_cnt++;	ISCSI_DBG_SESSION(session, "iscsi prep [%s cid %d sc %p cdb 0x%x "			  "itt 0x%x len %d cmdsn %d win %d]\n",			  sc->sc_data_direction == DMA_TO_DEVICE ?			  "write" : "read", conn->id, sc, sc->cmnd[0],			  task->itt, transfer_length,			  session->cmdsn,			  session->max_cmdsn - session->exp_cmdsn + 1);	return 0;} /** * iscsi_free_task - free a task * @task: iscsi cmd task * * Must be called with session back_lock. * This function returns the scsi command to scsi-ml or cleans * up mgmt tasks then returns the task to the pool. */static void iscsi_free_task(struct iscsi_task *task){	struct iscsi_conn *conn = task->conn;	struct iscsi_session *session = conn->session;	struct scsi_cmnd *sc = task->sc;	int oldstate = task->state; 	ISCSI_DBG_SESSION(session, "freeing task itt 0x%x state %d sc %p\n",			  task->itt, task->state, task->sc); 	session->tt->cleanup_task(task);	task->state = ISCSI_TASK_FREE;	task->sc = NULL;	/*	 * login task is preallocated so do not free	 */	if (conn->login_task == task)		return; 	kfifo_in(&session->cmdpool.queue, (void*)&task, sizeof(void*)); 	if (sc) {		/* SCSI eh reuses commands to verify us */		iscsi_cmd(sc)->task = NULL;		/*		 * queue command may call this to free the task, so		 * it will decide how to return sc to scsi-ml.		 */		if (oldstate != ISCSI_TASK_REQUEUE_SCSIQ)			scsi_done(sc);	}} bool iscsi_get_task(struct iscsi_task *task){	return refcount_inc_not_zero(&task->refcount);}EXPORT_SYMBOL_GPL(iscsi_get_task); /** * __iscsi_put_task - drop the refcount on a task * @task: iscsi_task to drop the refcount on * * The back_lock must be held when calling in case it frees the task. */void __iscsi_put_task(struct iscsi_task *task){

Lines 1–500 of 3937.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t