staticint 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.");
staticint 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.");
staticint 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.");
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);
staticvoid __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;
/** * 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.
*/ staticint 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
*/ staticint 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));
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));
/** * 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.
*/ staticvoid 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;
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);
}
}
/** * __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)
{ if (refcount_dec_and_test(&task->refcount))
iscsi_free_task(task);
}
EXPORT_SYMBOL_GPL(__iscsi_put_task);
if (refcount_dec_and_test(&task->refcount)) {
spin_lock_bh(&session->back_lock);
iscsi_free_task(task);
spin_unlock_bh(&session->back_lock);
}
}
EXPORT_SYMBOL_GPL(iscsi_put_task);
/** * iscsi_complete_task - finish a task * @task: iscsi cmd task * @state: state to complete task with * * Must be called with session back_lock.
*/ staticvoid iscsi_complete_task(struct iscsi_task *task, int state)
{ struct iscsi_conn *conn = task->conn;
if (READ_ONCE(conn->ping_task) == task)
WRITE_ONCE(conn->ping_task, NULL);
/* release get from queueing */
__iscsi_put_task(task);
}
/** * iscsi_complete_scsi_task - finish scsi task normally * @task: iscsi task for scsi cmd * @exp_cmdsn: expected cmd sn in cpu format * @max_cmdsn: max cmd sn in cpu format * * This is used when drivers do not need or cannot perform * lower level pdu processing. * * Called with session back_lock
*/ void iscsi_complete_scsi_task(struct iscsi_task *task,
uint32_t exp_cmdsn, uint32_t max_cmdsn)
{ struct iscsi_conn *conn = task->conn;
/* * Must be called with back and frwd lock
*/ staticbool cleanup_queued_task(struct iscsi_task *task)
{ struct iscsi_conn *conn = task->conn; bool early_complete = false;
/* * We might have raced where we handled a R2T early and got a response * but have not yet taken the task off the requeue list, then a TMF or * recovery happened and so we can still see it here.
*/ if (task->state == ISCSI_TASK_COMPLETED)
early_complete = true;
if (!list_empty(&task->running)) {
list_del_init(&task->running); /* * If it's on a list but still running this could be cleanup * from a TMF or session recovery.
*/ if (task->state == ISCSI_TASK_RUNNING ||
task->state == ISCSI_TASK_COMPLETED)
__iscsi_put_task(task);
}
if (conn->session->running_aborted_task == task) {
conn->session->running_aborted_task = NULL;
__iscsi_put_task(task);
}
if (conn->task == task) {
conn->task = NULL;
__iscsi_put_task(task);
}
return early_complete;
}
/* * session back and frwd lock must be held and if not called for a task that * is still pending or from the xmit thread, then xmit thread must be suspended
*/ staticvoid __fail_scsi_task(struct iscsi_task *task, int err)
{ struct iscsi_conn *conn = task->conn; struct scsi_cmnd *sc; int state;
if (cleanup_queued_task(task)) return;
if (task->state == ISCSI_TASK_PENDING) { /* * cmd never made it to the xmit thread, so we should not count * the cmd in the sequencing
*/
conn->session->queued_cmdsn--; /* it was never sent so just complete like normal */
state = ISCSI_TASK_COMPLETED;
} elseif (err == DID_TRANSPORT_DISRUPTED)
state = ISCSI_TASK_ABRT_SESS_RECOV; else
state = ISCSI_TASK_ABRT_TMF;
if (conn->session->state == ISCSI_STATE_LOGGING_OUT) return -ENOTCONN;
if (opcode != ISCSI_OP_LOGIN && opcode != ISCSI_OP_TEXT)
nop->exp_statsn = cpu_to_be32(conn->exp_statsn); /* * pre-format CmdSN for outgoing PDU.
*/
nop->cmdsn = cpu_to_be32(session->cmdsn); if (hdr->itt != RESERVED_ITT) { /* * TODO: We always use immediate for normal session pdus. * If we start to send tmfs or nops as non-immediate then * we should start checking the cmdsn numbers for mgmt tasks. * * During discovery sessions iscsid sends TEXT as non immediate, * but we always only send one PDU at a time.
*/ if (conn->c_stage == ISCSI_CONN_STARTED &&
!(hdr->opcode & ISCSI_OP_IMMEDIATE)) {
session->queued_cmdsn++;
session->cmdsn++;
}
}
if (session->tt->init_task && session->tt->init_task(task)) return -EIO;
if ((hdr->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT)
session->state = ISCSI_STATE_LOGGING_OUT;
/** * iscsi_alloc_mgmt_task - allocate and setup a mgmt task. * @conn: iscsi conn that the task will be sent on. * @hdr: iscsi pdu that will be sent. * @data: buffer for data segment if needed. * @data_size: length of data in bytes.
*/ staticstruct iscsi_task *
iscsi_alloc_mgmt_task(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, uint32_t data_size)
{ struct iscsi_session *session = conn->session;
uint8_t opcode = hdr->opcode & ISCSI_OPCODE_MASK; struct iscsi_task *task;
itt_t itt;
if (session->state == ISCSI_STATE_TERMINATE ||
!test_bit(ISCSI_CONN_FLAG_BOUND, &conn->flags)) return NULL;
if (opcode == ISCSI_OP_LOGIN || opcode == ISCSI_OP_TEXT) { /* * Login and Text are sent serially, in * request-followed-by-response sequence. * Same task can be used. Same ITT must be used. * Note that login_task is preallocated at conn_create().
*/ if (conn->login_task->state != ISCSI_TASK_FREE) {
iscsi_conn_printk(KERN_ERR, conn, "Login/Text in " "progress. Cannot start new task.\n"); return NULL;
}
if (data_size > ISCSI_DEF_MAX_RECV_SEG_LEN) {
iscsi_conn_printk(KERN_ERR, conn, "Invalid buffer len of %u for login task. Max len is %u\n", data_size, ISCSI_DEF_MAX_RECV_SEG_LEN); return NULL;
}
if (!kfifo_out(&session->cmdpool.queue,
(void*)&task, sizeof(void*))) return NULL;
} /* * released in complete pdu for task we expect a response for, and * released by the lld when it has transmitted the task for * pdus we do not expect a response for.
*/
refcount_set(&task->refcount, 1);
task->conn = conn;
task->sc = NULL;
INIT_LIST_HEAD(&task->running);
task->state = ISCSI_TASK_PENDING;
if (hdr->itt != RESERVED_ITT) { if (session->tt->parse_pdu_itt)
task->hdr->itt = itt; else
task->hdr->itt = build_itt(task->itt,
task->conn->session->age);
}
return task;
free_task:
iscsi_put_task(task); return NULL;
}
/** * iscsi_send_mgmt_task - Send task created with iscsi_alloc_mgmt_task. * @task: iscsi task to send. * * On failure this returns a non-zero error code, and the driver must free * the task with iscsi_put_task;
*/ staticint iscsi_send_mgmt_task(struct iscsi_task *task)
{ struct iscsi_conn *conn = task->conn; struct iscsi_session *session = conn->session; struct iscsi_host *ihost = shost_priv(conn->session->host); int rc = 0;
if (!ihost->workq) {
rc = iscsi_prep_mgmt_task(conn, task); if (rc) return rc;
/** * Transports that didn't implement check_protection * callback but still published T10-PI support to scsi-mid * deserve this BUG_ON.
**/
BUG_ON(!session->tt->check_protection);
ISCSI_DBG_SESSION(conn->session, "data in with status done " "[sc %p res %d itt 0x%x]\n",
sc, sc->result, task->itt);
conn->scsirsp_pdus_cnt++;
iscsi_complete_task(task, ISCSI_TASK_COMPLETED);
}
/** * iscsi_nop_out_rsp - SCSI NOP Response processing * @task: scsi command task * @nop: the nop structure * @data: where to put the data * @datalen: length of data * * iscsi_nop_out_rsp handles nop response from use or * from user space. called under back_lock
**/ staticint iscsi_nop_out_rsp(struct iscsi_task *task, struct iscsi_nopin *nop, char *data, int datalen)
{ struct iscsi_conn *conn = task->conn; int rc = 0;
if (READ_ONCE(conn->ping_task) != task) { /* * If this is not in response to one of our * nops then it must be from userspace.
*/ if (iscsi_recv_pdu(conn->cls_conn, (struct iscsi_hdr *)nop,
data, datalen))
rc = ISCSI_ERR_CONN_FAILED;
} else
mod_timer(&conn->transport_timer, jiffies + conn->recv_timeout);
iscsi_complete_task(task, ISCSI_TASK_COMPLETED); return rc;
}
switch (reject->reason) { case ISCSI_REASON_DATA_DIGEST_ERROR:
iscsi_conn_printk(KERN_ERR, conn, "pdu (op 0x%x itt 0x%x) rejected " "due to DataDigest error.\n",
opcode, rejected_pdu.itt); break; case ISCSI_REASON_IMM_CMD_REJECT:
iscsi_conn_printk(KERN_ERR, conn, "pdu (op 0x%x itt 0x%x) rejected. Too many " "immediate commands.\n",
opcode, rejected_pdu.itt); /* * We only send one TMF at a time so if the target could not * handle it, then it should get fixed (RFC mandates that * a target can handle one immediate TMF per conn). * * For nops-outs, we could have sent more than one if * the target is sending us lots of nop-ins
*/ if (opcode != ISCSI_OP_NOOP_OUT) return 0;
if (rejected_pdu.itt == cpu_to_be32(ISCSI_RESERVED_TAG)) { /* * nop-out in response to target's nop-out rejected. * Just resend.
*/ /* In RX path we are under back lock */
spin_unlock(&conn->session->back_lock);
spin_lock(&conn->session->frwd_lock);
iscsi_send_nopout(conn,
(struct iscsi_nopin*)&rejected_pdu);
spin_unlock(&conn->session->frwd_lock);
spin_lock(&conn->session->back_lock);
} else { struct iscsi_task *task; /* * Our nop as ping got dropped. We know the target * and transport are ok so just clean up
*/
task = iscsi_itt_to_task(conn, rejected_pdu.itt); if (!task) {
iscsi_conn_printk(KERN_ERR, conn, "Invalid pdu reject. Could " "not lookup rejected task.\n");
rc = ISCSI_ERR_BAD_ITT;
} else
rc = iscsi_nop_out_rsp(task,
(struct iscsi_nopin*)&rejected_pdu,
NULL, 0);
} break; default:
iscsi_conn_printk(KERN_ERR, conn, "pdu (op 0x%x itt 0x%x) rejected. Reason " "code 0x%x\n", rejected_pdu.opcode,
rejected_pdu.itt, reject->reason); break;
} return rc;
}
/** * iscsi_itt_to_task - look up task by itt * @conn: iscsi connection * @itt: itt * * This should be used for mgmt tasks like login and nops, or if * the LDD's itt space does not include the session age. * * The session back_lock must be held.
*/ struct iscsi_task *iscsi_itt_to_task(struct iscsi_conn *conn, itt_t itt)
{ struct iscsi_session *session = conn->session; int i;
if (itt == RESERVED_ITT) return NULL;
if (session->tt->parse_pdu_itt)
session->tt->parse_pdu_itt(conn, itt, &i, NULL); else
i = get_itt(itt); if (i >= session->cmds_max) return NULL;
/** * __iscsi_complete_pdu - complete pdu * @conn: iscsi conn * @hdr: iscsi header * @data: data buffer * @datalen: len of data buffer * * Completes pdu processing by freeing any resources allocated at * queuecommand or send generic. session back_lock must be held and verify * itt must have been called.
*/ int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, int datalen)
{ struct iscsi_session *session = conn->session; int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0; struct iscsi_task *task;
uint32_t itt;
if (hdr->itt != RESERVED_ITT)
itt = get_itt(hdr->itt); else
itt = ~0U;
ISCSI_DBG_SESSION(session, "[op 0x%x cid %d itt 0x%x len %d]\n",
opcode, conn->id, itt, datalen);
if (itt == ~0U) {
iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr);
switch(opcode) { case ISCSI_OP_NOOP_IN: if (datalen) {
rc = ISCSI_ERR_PROTO; break;
}
if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) break;
/* In RX path we are under back lock */
spin_unlock(&session->back_lock);
spin_lock(&session->frwd_lock);
iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
spin_unlock(&session->frwd_lock);
spin_lock(&session->back_lock); break; case ISCSI_OP_REJECT:
rc = iscsi_handle_reject(conn, hdr, data, datalen); break; case ISCSI_OP_ASYNC_EVENT:
conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
rc = ISCSI_ERR_CONN_FAILED; break; default:
rc = ISCSI_ERR_BAD_OPCODE; break;
} goto out;
}
switch(opcode) { case ISCSI_OP_SCSI_CMD_RSP: case ISCSI_OP_SCSI_DATA_IN:
task = iscsi_itt_to_ctask(conn, hdr->itt); if (!task) return ISCSI_ERR_BAD_ITT;
task->last_xfer = jiffies; break; case ISCSI_OP_R2T: /* * LLD handles R2Ts if they need to.
*/ return 0; case ISCSI_OP_LOGOUT_RSP: case ISCSI_OP_LOGIN_RSP: case ISCSI_OP_TEXT_RSP: case ISCSI_OP_SCSI_TMFUNC_RSP: case ISCSI_OP_NOOP_IN:
task = iscsi_itt_to_task(conn, hdr->itt); if (!task) return ISCSI_ERR_BAD_ITT; break; default: return ISCSI_ERR_BAD_OPCODE;
}
switch(opcode) { case ISCSI_OP_SCSI_CMD_RSP:
iscsi_scsi_cmd_rsp(conn, hdr, task, data, datalen); break; case ISCSI_OP_SCSI_DATA_IN:
iscsi_data_in_rsp(conn, hdr, task); break; case ISCSI_OP_LOGOUT_RSP:
iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr); if (datalen) {
rc = ISCSI_ERR_PROTO; break;
}
conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; goto recv_pdu; case ISCSI_OP_LOGIN_RSP: case ISCSI_OP_TEXT_RSP:
iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr); /* * login related PDU's exp_statsn is handled in * userspace
*/ goto recv_pdu; case ISCSI_OP_SCSI_TMFUNC_RSP:
iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr); if (datalen) {
rc = ISCSI_ERR_PROTO; break;
}
int iscsi_verify_itt(struct iscsi_conn *conn, itt_t itt)
{ struct iscsi_session *session = conn->session; int age = 0, i = 0;
if (itt == RESERVED_ITT) return 0;
if (session->tt->parse_pdu_itt)
session->tt->parse_pdu_itt(conn, itt, &i, &age); else {
i = get_itt(itt);
age = ((__force u32)itt >> ISCSI_AGE_SHIFT) & ISCSI_AGE_MASK;
}
if (age != session->age) {
iscsi_conn_printk(KERN_ERR, conn, "received itt %x expected session age (%x)\n",
(__force u32)itt, session->age); return ISCSI_ERR_BAD_ITT;
}
if (i >= session->cmds_max) {
iscsi_conn_printk(KERN_ERR, conn, "received invalid itt index %u (max cmds " "%u.\n", i, session->cmds_max); return ISCSI_ERR_BAD_ITT;
} return 0;
}
EXPORT_SYMBOL_GPL(iscsi_verify_itt);
/** * iscsi_itt_to_ctask - look up ctask by itt * @conn: iscsi connection * @itt: itt * * This should be used for cmd tasks. * * The session back_lock must be held.
*/ struct iscsi_task *iscsi_itt_to_ctask(struct iscsi_conn *conn, itt_t itt)
{ struct iscsi_task *task;
if (iscsi_verify_itt(conn, itt)) return NULL;
task = iscsi_itt_to_task(conn, itt); if (!task || !task->sc) return NULL;
if (iscsi_cmd(task->sc)->age != conn->session->age) {
iscsi_session_printk(KERN_ERR, conn->session, "task's session age %d, expected %d\n",
iscsi_cmd(task->sc)->age, conn->session->age); return NULL;
}
iscsi_get_conn(conn->cls_conn);
spin_unlock_bh(&session->frwd_lock); /* * if the host is being removed bypass the connection * recovery initialization because we are going to kill * the session.
*/ if (err == ISCSI_ERR_INVALID_HOST)
iscsi_conn_error_event(conn->cls_conn, err); else
iscsi_conn_failure(conn, err);
iscsi_put_conn(conn->cls_conn);
}
EXPORT_SYMBOL_GPL(iscsi_session_failure);
if (!conn->task) { /* * Take a ref so we can access it after xmit_task(). * * This should never fail because the failure paths will have * stopped the xmit thread.
*/ if (!iscsi_get_task(task)) {
WARN_ON_ONCE(1); return 0;
}
} else { /* Already have a ref from when we failed to send it last call */
conn->task = NULL;
}
/* * If this was a requeue for a R2T we have an extra ref on the task in * case a bad target sends a cmd rsp before we have handled the task.
*/ if (was_requeue)
iscsi_put_task(task);
/* * Do this after dropping the extra ref because if this was a requeue * it's removed from that list and cleanup_queued_task would miss it.
*/ if (test_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags)) { /* * Save the task and ref in case we weren't cleaning up this * task and get woken up again.
*/
conn->task = task; return -ENODATA;
}
spin_unlock_bh(&conn->session->frwd_lock);
rc = conn->session->tt->xmit_task(task);
spin_lock_bh(&conn->session->frwd_lock); if (!rc) { /* done with this task */
task->last_xfer = jiffies;
} else { /* * get an extra ref that is released next time we access it * as conn->task above.
*/
iscsi_get_task(task);
conn->task = task;
}
iscsi_put_task(task); return rc;
}
/** * iscsi_requeue_task - requeue task to run from session workqueue * @task: task to requeue * * Callers must have taken a ref to the task that is going to be requeued.
*/ void iscsi_requeue_task(struct iscsi_task *task)
{ struct iscsi_conn *conn = task->conn;
/* * this may be on the requeue list already if the xmit_task callout * is handling the r2ts while we are adding new ones
*/
spin_lock_bh(&conn->session->frwd_lock); if (list_empty(&task->running)) {
list_add_tail(&task->running, &conn->requeue);
} else { /* * Don't need the extra ref since it's already requeued and * has a ref.
*/
iscsi_put_task(task);
}
iscsi_conn_queue_xmit(conn);
spin_unlock_bh(&conn->session->frwd_lock);
}
EXPORT_SYMBOL_GPL(iscsi_requeue_task);
/** * iscsi_data_xmit - xmit any command into the scheduled connection * @conn: iscsi connection * * Notes: * The function can return -EAGAIN in which case the caller must * re-schedule it again later or recover. '0' return code means * successful xmit.
**/ staticint iscsi_data_xmit(struct iscsi_conn *conn)
{ struct iscsi_task *task; int rc = 0;
if (conn->task) {
rc = iscsi_xmit_task(conn, conn->task, false); if (rc) goto done;
}
/* * process mgmt pdus like nops before commands since we should * only have one nop-out as a ping from us and targets should not * overflow us with nop-ins
*/
check_mgmt: while (!list_empty(&conn->mgmtqueue)) {
task = list_entry(conn->mgmtqueue.next, struct iscsi_task,
running);
list_del_init(&task->running); if (iscsi_prep_mgmt_task(conn, task)) { /* regular RX path uses back_lock */
spin_lock_bh(&conn->session->back_lock);
__iscsi_put_task(task);
spin_unlock_bh(&conn->session->back_lock); continue;
}
rc = iscsi_xmit_task(conn, task, false); if (rc) goto done;
}
check_requeue: while (!list_empty(&conn->requeue)) { /* * we always do fastlogout - conn stop code will clean up.
*/ if (conn->session->state == ISCSI_STATE_LOGGING_OUT) break;
if (iscsi_check_tmf_restrictions(task, ISCSI_OP_SCSI_DATA_OUT)) break;
list_del_init(&task->running);
rc = iscsi_xmit_task(conn, task, true); if (rc) goto done; if (!list_empty(&conn->mgmtqueue)) goto check_mgmt;
}
/* process pending command queue */ while (!list_empty(&conn->cmdqueue)) {
task = list_entry(conn->cmdqueue.next, struct iscsi_task,
running);
list_del_init(&task->running); if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
fail_scsi_task(task, DID_IMM_RETRY); continue;
}
rc = iscsi_prep_scsi_cmd_pdu(task); if (rc) { if (rc == -ENOMEM || rc == -EACCES)
fail_scsi_task(task, DID_IMM_RETRY); else
fail_scsi_task(task, DID_ABORT); continue;
}
rc = iscsi_xmit_task(conn, task, false); if (rc) goto done; /* * we could continuously get new task requests so * we need to check the mgmt queue for nops that need to * be sent to aviod starvation
*/ if (!list_empty(&conn->mgmtqueue)) goto check_mgmt; if (!list_empty(&conn->requeue)) goto check_requeue;
}
if (session->state != ISCSI_STATE_LOGGED_IN) { /* * to handle the race between when we set the recovery state * and block the session we requeue here (commands could * be entering our queuecommand while a block is starting * up because the block code is not locked)
*/ switch (session->state) { case ISCSI_STATE_FAILED: /* * cmds should fail during shutdown, if the session * state is bad, allowing completion to happen
*/ if (unlikely(system_state != SYSTEM_RUNNING)) {
reason = FAILURE_SESSION_FAILED;
sc->result = DID_NO_CONNECT << 16; break;
}
fallthrough; case ISCSI_STATE_IN_RECOVERY:
reason = FAILURE_SESSION_IN_RECOVERY;
sc->result = DID_IMM_RETRY << 16; break; case ISCSI_STATE_LOGGING_OUT:
reason = FAILURE_SESSION_LOGGING_OUT;
sc->result = DID_IMM_RETRY << 16; break; case ISCSI_STATE_RECOVERY_FAILED:
reason = FAILURE_SESSION_RECOVERY_TIMEOUT;
sc->result = DID_TRANSPORT_FAILFAST << 16; break; case ISCSI_STATE_TERMINATE:
reason = FAILURE_SESSION_TERMINATE;
sc->result = DID_NO_CONNECT << 16; break; default:
reason = FAILURE_SESSION_FREED;
sc->result = DID_NO_CONNECT << 16;
} goto fault;
}
/* * block eh thread until: * * 1) tmf response * 2) tmf timeout * 3) session is terminated or restarted or userspace has * given up on recovery
*/
wait_event_interruptible(session->ehwait, age != session->age ||
session->state != ISCSI_STATE_LOGGED_IN ||
session->tmf_state != TMF_QUEUED); if (signal_pending(current))
flush_signals(current);
timer_delete_sync(&session->tmf_timer);
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->frwd_lock); /* if the session drops it will clean up the task */ if (age != session->age ||
session->state != ISCSI_STATE_LOGGED_IN) return -ENOTCONN; return 0;
}
/* * Fail commands. session frwd lock held and xmit thread flushed.
*/ staticvoid fail_scsi_tasks(struct iscsi_conn *conn, u64 lun, int error)
{ struct iscsi_session *session = conn->session; struct iscsi_task *task; int i;
restart_cmd_loop:
spin_lock_bh(&session->back_lock); for (i = 0; i < session->cmds_max; i++) {
task = session->cmds[i]; if (!task->sc || task->state == ISCSI_TASK_FREE) continue;
if (lun != -1 && lun != task->sc->device->lun) continue; /* * The cmd is completing but if this is called from an eh * callout path then when we return scsi-ml owns the cmd. Wait * for the completion path to finish freeing the cmd.
*/ if (!iscsi_get_task(task)) {
spin_unlock_bh(&session->back_lock);
spin_unlock_bh(&session->frwd_lock);
udelay(ISCSI_CMD_COMPL_WAIT);
spin_lock_bh(&session->frwd_lock); goto restart_cmd_loop;
}
ISCSI_DBG_SESSION(session, "failing sc %p itt 0x%x state %d\n",
task->sc, task->itt, task->state);
__fail_scsi_task(task, error);
__iscsi_put_task(task);
}
spin_unlock_bh(&session->back_lock);
}
/** * iscsi_suspend_queue - suspend iscsi_queuecommand * @conn: iscsi conn to stop queueing IO on * * This grabs the session frwd_lock to make sure no one is in * xmit_task/queuecommand, and then sets suspend to prevent * new commands from being queued. This only needs to be called * by offload drivers that need to sync a path like ep disconnect * with the iscsi_queuecommand/xmit_task. To start IO again libiscsi * will call iscsi_start_tx and iscsi_unblock_session when in FFP.
*/ void iscsi_suspend_queue(struct iscsi_conn *conn)
{
spin_lock_bh(&conn->session->frwd_lock);
set_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags);
spin_unlock_bh(&conn->session->frwd_lock);
}
EXPORT_SYMBOL_GPL(iscsi_suspend_queue);
/** * iscsi_suspend_tx - suspend iscsi_data_xmit * @conn: iscsi conn to stop processing IO on. * * This function sets the suspend bit to prevent iscsi_data_xmit * from sending new IO, and if work is queued on the xmit thread * it will wait for it to be completed.
*/ void iscsi_suspend_tx(struct iscsi_conn *conn)
{ struct Scsi_Host *shost = conn->session->host; struct iscsi_host *ihost = shost_priv(shost);
set_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags); if (ihost->workq)
flush_work(&conn->xmitwork);
}
EXPORT_SYMBOL_GPL(iscsi_suspend_tx);
set_bit(ISCSI_CONN_FLAG_SUSPEND_RX, &conn->flags); if (ihost->workq)
flush_work(&conn->recvwork);
}
EXPORT_SYMBOL_GPL(iscsi_suspend_rx);
/* * We want to make sure a ping is in flight. It has timed out. * And we are not busy processing a pdu that is making * progress but got started before the ping and is taking a while * to complete so the ping is just stuck behind it in a queue.
*/ staticint iscsi_has_ping_timed_out(struct iscsi_conn *conn)
{ if (READ_ONCE(conn->ping_task) &&
time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) +
(conn->ping_timeout * HZ), jiffies)) return 1; else return 0;
}
spin_lock_bh(&session->frwd_lock);
spin_lock(&session->back_lock);
task = iscsi_cmd(sc)->task; if (!task) { /* * Raced with completion. Blk layer has taken ownership * so let timeout code complete it now.
*/
rc = SCSI_EH_NOT_HANDLED;
spin_unlock(&session->back_lock); goto done;
} if (!iscsi_get_task(task)) { /* * Racing with the completion path right now, so give it more * time so that path can complete it like normal.
*/
rc = SCSI_EH_RESET_TIMER;
task = NULL;
spin_unlock(&session->back_lock); goto done;
}
spin_unlock(&session->back_lock);
if (session->state != ISCSI_STATE_LOGGED_IN) { /* * During shutdown, if session is prematurely disconnected, * recovery won't happen and there will be hung cmds. Not * handling cmds would trigger EH, also bad in this case. * Instead, handle cmd, allow completion to happen and let * upper layer to deal with the result.
*/ if (unlikely(system_state != SYSTEM_RUNNING)) {
sc->result = DID_NO_CONNECT << 16;
ISCSI_DBG_EH(session, "sc on shutdown, handled\n");
rc = SCSI_EH_NOT_HANDLED; goto done;
} /* * We are probably in the middle of iscsi recovery so let * that complete and handle the error.
*/
rc = SCSI_EH_RESET_TIMER; goto done;
}
conn = session->leadconn; if (!conn) { /* In the middle of shuting down */
rc = SCSI_EH_RESET_TIMER; goto done;
}
/* * If we have sent (at least queued to the network layer) a pdu or * recvd one for the task since the last timeout ask for * more time. If on the next timeout we have not made progress * we can check if it is the task or connection when we send the * nop as a ping.
*/ if (time_after(task->last_xfer, task->last_timeout)) {
ISCSI_DBG_EH(session, "Command making progress. Asking " "scsi-ml for more time to complete. " "Last data xfer at %lu. Last timeout was at " "%lu\n.", task->last_xfer, task->last_timeout);
task->have_checked_conn = false;
rc = SCSI_EH_RESET_TIMER; goto done;
}
if (!conn->recv_timeout && !conn->ping_timeout) goto done; /* * if the ping timedout then we are in the middle of cleaning up * and can let the iscsi eh handle it
*/ if (iscsi_has_ping_timed_out(conn)) {
rc = SCSI_EH_RESET_TIMER; goto done;
}
spin_lock(&session->back_lock); for (i = 0; i < conn->session->cmds_max; i++) {
running_task = conn->session->cmds[i]; if (!running_task->sc || running_task == task ||
running_task->state != ISCSI_TASK_RUNNING) continue;
/* * Only check if cmds started before this one have made * progress, or this could never fail
*/ if (time_after(running_task->sc->jiffies_at_alloc,
task->sc->jiffies_at_alloc)) continue;
if (time_after(running_task->last_xfer, task->last_timeout)) { /* * This task has not made progress, but a task * started before us has transferred data since * we started/last-checked. We could be queueing * too many tasks or the LU is bad. * * If the device is bad the cmds ahead of us on * other devs will complete, and this loop will * eventually fail starting the scsi eh.
*/
ISCSI_DBG_EH(session, "Command has not made progress " "but commands ahead of it have. " "Asking scsi-ml for more time to " "complete. Our last xfer vs running task " "last xfer %lu/%lu. Last check %lu.\n",
task->last_xfer, running_task->last_xfer,
task->last_timeout);
spin_unlock(&session->back_lock);
rc = SCSI_EH_RESET_TIMER; goto done;
}
}
spin_unlock(&session->back_lock);
/* Assumes nop timeout is shorter than scsi cmd timeout */ if (task->have_checked_conn) goto done;
/* * Checking the transport already or nop from a cmd timeout still * running
*/ if (READ_ONCE(conn->ping_task)) {
task->have_checked_conn = true;
rc = SCSI_EH_RESET_TIMER; goto done;
}
/* Make sure there is a transport check done */
iscsi_send_nopout(conn, NULL);
task->have_checked_conn = true;
rc = SCSI_EH_RESET_TIMER;
/** * iscsi_conn_unbind - prevent queueing to conn. * @cls_conn: iscsi conn ep is bound to. * @is_active: is the conn in use for boot or is this for EH/termination * * This must be called by drivers implementing the ep_disconnect callout. * It disables queueing to the connection from libiscsi in preparation for * an ep_disconnect call.
*/ void iscsi_conn_unbind(struct iscsi_cls_conn *cls_conn, bool is_active)
{ struct iscsi_session *session; struct iscsi_conn *conn;
if (!cls_conn) return;
conn = cls_conn->dd_data;
session = conn->session; /* * Wait for iscsi_eh calls to exit. We don't wait for the tmf to * complete or timeout. The caller just wants to know what's running * is everything that needs to be cleaned up, and no cmds will be * queued.
*/
mutex_lock(&session->eh_mutex);
if (!is_active) { /* * if logout timed out before userspace could even send a PDU * the state might still be in ISCSI_STATE_LOGGED_IN and * allowing new cmds and TMFs.
*/ if (session->state == ISCSI_STATE_LOGGED_IN)
iscsi_set_conn_failed(conn);
}
spin_unlock_bh(&session->frwd_lock);
mutex_unlock(&session->eh_mutex);
}
EXPORT_SYMBOL_GPL(iscsi_conn_unbind);
completion_check:
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->frwd_lock); /* * if session was ISCSI_STATE_IN_RECOVERY then we may not have * got the command.
*/ if (!iscsi_cmd(sc)->task) {
ISCSI_DBG_EH(session, "sc never reached iscsi layer or " "it completed.\n");
spin_unlock_bh(&session->frwd_lock);
mutex_unlock(&session->eh_mutex); return SUCCESS;
}
/* * If we are not logged in or we have started a new session * then let the host reset code handle this
*/ if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN ||
iscsi_cmd(sc)->age != session->age) {
spin_unlock_bh(&session->frwd_lock);
mutex_unlock(&session->eh_mutex);
ISCSI_DBG_EH(session, "failing abort due to dropped " "session.\n"); return FAILED;
}
spin_lock(&session->back_lock);
task = iscsi_cmd(sc)->task; if (!task || !task->sc) { /* task completed before time out */
ISCSI_DBG_EH(session, "sc completed while abort in progress\n");
if (!iscsi_get_task(task)) {
spin_unlock(&session->back_lock);
spin_unlock_bh(&session->frwd_lock);
mutex_unlock(&session->eh_mutex); /* We are just about to call iscsi_free_task so wait for it. */
udelay(ISCSI_CMD_COMPL_WAIT); goto completion_check;
}
ISCSI_DBG_EH(session, "aborting [sc %p itt 0x%x]\n", sc, task->itt);
conn = session->leadconn;
iscsi_get_conn(conn->cls_conn);
conn->eh_abort_cnt++;
age = session->age;
spin_unlock(&session->back_lock);
if (task->state == ISCSI_TASK_PENDING) {
fail_scsi_task(task, DID_ABORT); goto success;
}
/* only have one tmf outstanding at a time */
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.