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 */ if (session->tmf_state != TMF_INITIAL) goto failed;
session->tmf_state = TMF_QUEUED;
if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) goto failed;
switch (session->tmf_state) { case TMF_SUCCESS:
spin_unlock_bh(&session->frwd_lock); /* * stop tx side incase the target had sent a abort rsp but * the initiator was still writing out data.
*/
iscsi_suspend_tx(conn); /* * we do not stop the recv side because targets have been * good and have never sent us a successful tmf response * then sent more data for the cmd.
*/
spin_lock_bh(&session->frwd_lock);
fail_scsi_task(task, DID_ABORT);
session->tmf_state = TMF_INITIAL;
memset(hdr, 0, sizeof(*hdr));
spin_unlock_bh(&session->frwd_lock);
iscsi_start_tx(conn); goto success_unlocked; case TMF_TIMEDOUT:
session->running_aborted_task = task;
spin_unlock_bh(&session->frwd_lock);
iscsi_conn_failure(conn, ISCSI_ERR_SCSI_EH_SESSION_RST); goto failed_unlocked; case TMF_NOT_FOUND: if (iscsi_task_is_completed(task)) {
session->tmf_state = TMF_INITIAL;
memset(hdr, 0, sizeof(*hdr)); /* task completed before tmf abort response */
ISCSI_DBG_EH(session, "sc completed while abort in " "progress\n"); goto success;
}
fallthrough; default:
session->tmf_state = TMF_INITIAL; goto failed;
}
failed:
spin_unlock_bh(&session->frwd_lock);
failed_unlocked:
ISCSI_DBG_EH(session, "abort failed [sc %p itt 0x%x]\n", sc,
task ? task->itt : 0); /* * The driver might be accessing the task so hold the ref. The conn * stop cleanup will drop the ref after ep_disconnect so we know the * driver's no longer touching the task.
*/ if (!session->running_aborted_task)
iscsi_put_task(task);
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->frwd_lock); /* * Just check if we are not logged in. We cannot check for * the phase because the reset could come from a ioctl.
*/ if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN) goto unlock;
conn = session->leadconn;
/* only have one tmf outstanding at a time */ if (session->tmf_state != TMF_INITIAL) goto unlock;
session->tmf_state = TMF_QUEUED;
/** * iscsi_eh_session_reset - drop session and attempt relogin * @sc: scsi command * * This function will wait for a relogin, session termination from * userspace, or a recovery/replacement timeout.
*/ int iscsi_eh_session_reset(struct scsi_cmnd *sc)
{ struct iscsi_cls_session *cls_session; struct iscsi_session *session; struct iscsi_conn *conn;
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->frwd_lock); /* * Just check if we are not logged in. We cannot check for * the phase because the reset could come from a ioctl.
*/ if (!session->leadconn || session->state != ISCSI_STATE_LOGGED_IN) goto unlock;
conn = session->leadconn;
/* only have one tmf outstanding at a time */ if (session->tmf_state != TMF_INITIAL) goto unlock;
session->tmf_state = TMF_QUEUED;
/** * iscsi_eh_recover_target - reset target and possibly the session * @sc: scsi command * * This will attempt to send a warm target reset. If that fails, * we will escalate to ERL0 session recovery.
*/ int iscsi_eh_recover_target(struct scsi_cmnd *sc)
{ int rc;
/* * Pre-allocate a pool of @max items of @item_size. By default, the pool * should be accessed via kfifo_{get,put} on q->queue. * Optionally, the caller can obtain the array of object pointers * by passing in a non-NULL @items pointer
*/ int
iscsi_pool_init(struct iscsi_pool *q, int max, void ***items, int item_size)
{ int i, num_arrays = 1;
memset(q, 0, sizeof(*q));
q->max = max;
/* If the user passed an items pointer, he wants a copy of
* the array. */ if (items)
num_arrays++;
q->pool = kvcalloc(num_arrays * max, sizeof(void *), GFP_KERNEL); if (q->pool == NULL) return -ENOMEM;
kfifo_init(&q->queue, (void*)q->pool, max * sizeof(void*));
for (i = 0; i < max; i++) {
q->pool[i] = kzalloc(item_size, GFP_KERNEL); if (q->pool[i] == NULL) {
q->max = i; goto enomem;
}
kfifo_in(&q->queue, (void*)&q->pool[i], sizeof(void*));
}
if (items) {
*items = q->pool + max;
memcpy(*items, q->pool, max * sizeof(void *));
}
void iscsi_pool_free(struct iscsi_pool *q)
{ int i;
for (i = 0; i < q->max; i++)
kfree(q->pool[i]);
kvfree(q->pool);
}
EXPORT_SYMBOL_GPL(iscsi_pool_free);
int iscsi_host_get_max_scsi_cmds(struct Scsi_Host *shost,
uint16_t requested_cmds_max)
{ int scsi_cmds, total_cmds = requested_cmds_max;
check: if (!total_cmds)
total_cmds = ISCSI_DEF_XMIT_CMDS_MAX; /* * The iscsi layer needs some tasks for nop handling and tmfs, * so the cmds_max must at least be greater than ISCSI_MGMT_CMDS_MAX * + 1 command for scsi IO.
*/ if (total_cmds < ISCSI_TOTAL_CMDS_MIN) {
printk(KERN_ERR "iscsi: invalid max cmds of %d. Must be a power of two that is at least %d.\n",
total_cmds, ISCSI_TOTAL_CMDS_MIN); return -EINVAL;
}
if (total_cmds > ISCSI_TOTAL_CMDS_MAX) {
printk(KERN_INFO "iscsi: invalid max cmds of %d. Must be a power of 2 less than or equal to %d. Using %d.\n",
requested_cmds_max, ISCSI_TOTAL_CMDS_MAX,
ISCSI_TOTAL_CMDS_MAX);
total_cmds = ISCSI_TOTAL_CMDS_MAX;
}
if (!is_power_of_2(total_cmds)) {
total_cmds = rounddown_pow_of_two(total_cmds); if (total_cmds < ISCSI_TOTAL_CMDS_MIN) {
printk(KERN_ERR "iscsi: invalid max cmds of %d. Must be a power of 2 greater than %d.\n", requested_cmds_max, ISCSI_TOTAL_CMDS_MIN); return -EINVAL;
}
printk(KERN_INFO "iscsi: invalid max cmds %d. Must be a power of 2. Rounding max cmds down to %d.\n",
requested_cmds_max, total_cmds);
}
printk(KERN_INFO "iscsi: requested max cmds %u is higher than driver limit. Using driver limit %u\n",
requested_cmds_max, shost->can_queue); goto check;
}
/** * iscsi_host_add - add host to system * @shost: scsi host * @pdev: parent device * * This should be called by partial offload and software iscsi drivers * to add a host to the system.
*/ int iscsi_host_add(struct Scsi_Host *shost, struct device *pdev)
{ if (!shost->can_queue)
shost->can_queue = ISCSI_DEF_XMIT_CMDS_MAX;
if (!shost->cmd_per_lun)
shost->cmd_per_lun = ISCSI_DEF_CMD_PER_LUN;
/** * iscsi_host_alloc - allocate a host and driver data * @sht: scsi host template * @dd_data_size: driver host data size * @xmit_can_sleep: bool indicating if LLD will queue IO from a work queue * * This should be called by partial offload and software iscsi drivers. * To access the driver specific memory use the iscsi_host_priv() macro.
*/ struct Scsi_Host *iscsi_host_alloc(conststruct scsi_host_template *sht, int dd_data_size, bool xmit_can_sleep)
{ struct Scsi_Host *shost; struct iscsi_host *ihost;
/** * iscsi_host_remove - remove host and sessions * @shost: scsi host * @is_shutdown: true if called from a driver shutdown callout * * If there are any sessions left, this will initiate the removal and wait * for the completion.
*/ void iscsi_host_remove(struct Scsi_Host *shost, bool is_shutdown)
{ struct iscsi_host *ihost = shost_priv(shost); unsignedlong flags;
/** * iscsi_session_setup - create iscsi cls session and host and session * @iscsit: iscsi transport template * @shost: scsi host * @cmds_max: session can queue * @dd_size: private driver data size, added to session allocation size * @cmd_task_size: LLD task private data size * @initial_cmdsn: initial CmdSN * @id: target ID to add to this session * * This can be used by software iscsi_transports that allocate * a session per scsi host. * * Callers should set cmds_max to the largest total numer (mgmt + scsi) of * tasks they support. The iscsi layer reserves ISCSI_MGMT_CMDS_MAX tasks * for nop handling and login/logout requests.
*/ struct iscsi_cls_session *
iscsi_session_setup(struct iscsi_transport *iscsit, struct Scsi_Host *shost,
uint16_t cmds_max, int dd_size, int cmd_task_size,
uint32_t initial_cmdsn, unsignedint id)
{ struct iscsi_host *ihost = shost_priv(shost); struct iscsi_session *session; struct iscsi_cls_session *cls_session; int cmd_i, scsi_cmds; unsignedlong flags;
iscsi_remove_session(cls_session); /* * host removal only has to wait for its children to be removed from * sysfs, and iscsi_tcp needs to do iscsi_host_remove before freeing * the session, so drop the session count here.
*/
iscsi_host_dec_session_cnt(shost);
}
EXPORT_SYMBOL_GPL(iscsi_session_remove);
/* allocate login_task used for the login/text sequences */
spin_lock_bh(&session->frwd_lock); if (!kfifo_out(&session->cmdpool.queue,
(void*)&conn->login_task, sizeof(void*))) {
spin_unlock_bh(&session->frwd_lock); goto login_task_alloc_fail;
}
spin_unlock_bh(&session->frwd_lock);
data = (char *) __get_free_pages(GFP_KERNEL,
get_order(ISCSI_DEF_MAX_RECV_SEG_LEN)); if (!data) goto login_task_data_alloc_fail;
conn->login_task->data = conn->data = data;
err = iscsi_add_conn(cls_conn); if (err) goto login_task_add_dev_fail;
/** * iscsi_conn_teardown - teardown iscsi connection * @cls_conn: iscsi class connection * * TODO: we may need to make this into a two step process * like scsi-mls remove + put host
*/ void iscsi_conn_teardown(struct iscsi_cls_conn *cls_conn)
{ struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_session *session = conn->session;
iscsi_remove_conn(cls_conn);
timer_delete_sync(&conn->transport_timer);
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->frwd_lock);
conn->c_stage = ISCSI_CONN_CLEANUP_WAIT; if (session->leadconn == conn) { /* * leading connection? then give up on recovery.
*/
session->state = ISCSI_STATE_TERMINATE;
wake_up(&session->ehwait);
}
spin_unlock_bh(&session->frwd_lock);
/* flush queued up work because we free the connection below */
iscsi_suspend_tx(conn);
staticvoid
fail_mgmt_tasks(struct iscsi_session *session, struct iscsi_conn *conn)
{ struct iscsi_task *task; int i, state;
for (i = 0; i < conn->session->cmds_max; i++) {
task = conn->session->cmds[i]; if (task->sc) continue;
if (task->state == ISCSI_TASK_FREE) continue;
ISCSI_DBG_SESSION(conn->session, "failing mgmt itt 0x%x state %d\n",
task->itt, task->state);
spin_lock_bh(&session->back_lock); if (cleanup_queued_task(task)) {
spin_unlock_bh(&session->back_lock); continue;
}
state = ISCSI_TASK_ABRT_SESS_RECOV; if (task->state == ISCSI_TASK_PENDING)
state = ISCSI_TASK_COMPLETED;
iscsi_complete_task(task, state);
spin_unlock_bh(&session->back_lock);
}
}
void iscsi_conn_stop(struct iscsi_cls_conn *cls_conn, int flag)
{ struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_session *session = conn->session; int old_stop_stage;
/* * When this is called for the in_login state, we only want to clean * up the login task and connection. We do not need to block and set * the recovery state again
*/ if (flag == STOP_CONN_TERM)
session->state = ISCSI_STATE_TERMINATE; elseif (conn->stop_stage != STOP_CONN_RECOVER)
session->state = ISCSI_STATE_IN_RECOVERY;
/* * for connection level recovery we should not calculate * header digest. conn->hdr_size used for optimization * in hdr_extract() and will be re-negotiated at * set_param() time.
*/ if (flag == STOP_CONN_RECOVER) {
conn->hdrdgst_en = 0;
conn->datadgst_en = 0; if (session->state == ISCSI_STATE_IN_RECOVERY &&
old_stop_stage != STOP_CONN_RECOVER) {
ISCSI_DBG_SESSION(session, "blocking session\n");
iscsi_block_session(session->cls_session);
}
}
/* * The target could have reduced it's window size between logins, so * we have to reset max/exp cmdsn so we can see the new values.
*/
spin_lock_bh(&session->back_lock);
session->max_cmdsn = session->exp_cmdsn = session->cmdsn + 1;
spin_unlock_bh(&session->back_lock); /* * Unblock xmitworker(), Login Phase will pass through.
*/
clear_bit(ISCSI_CONN_FLAG_SUSPEND_RX, &conn->flags);
clear_bit(ISCSI_CONN_FLAG_SUSPEND_TX, &conn->flags); return 0;
}
EXPORT_SYMBOL_GPL(iscsi_conn_bind);
int iscsi_switch_str_param(char **param, char *new_val_buf)
{ char *new_val;
if (*param) { if (!strcmp(*param, new_val_buf)) return 0;
}
new_val = kstrdup(new_val_buf, GFP_NOIO); if (!new_val) return -ENOMEM;
int iscsi_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf, int buflen)
{ struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_session *session = conn->session; int val;
switch(param) { case ISCSI_PARAM_FAST_ABORT:
sscanf(buf, "%d", &session->fast_abort); break; case ISCSI_PARAM_ABORT_TMO:
sscanf(buf, "%d", &session->abort_timeout); break; case ISCSI_PARAM_LU_RESET_TMO:
sscanf(buf, "%d", &session->lu_reset_timeout); break; case ISCSI_PARAM_TGT_RESET_TMO:
sscanf(buf, "%d", &session->tgt_reset_timeout); break; case ISCSI_PARAM_PING_TMO:
sscanf(buf, "%d", &conn->ping_timeout); break; case ISCSI_PARAM_RECV_TMO:
sscanf(buf, "%d", &conn->recv_timeout); break; case ISCSI_PARAM_MAX_RECV_DLENGTH:
sscanf(buf, "%d", &conn->max_recv_dlength); break; case ISCSI_PARAM_MAX_XMIT_DLENGTH:
sscanf(buf, "%d", &conn->max_xmit_dlength); break; case ISCSI_PARAM_HDRDGST_EN:
sscanf(buf, "%d", &conn->hdrdgst_en); break; case ISCSI_PARAM_DATADGST_EN:
sscanf(buf, "%d", &conn->datadgst_en); break; case ISCSI_PARAM_INITIAL_R2T_EN:
sscanf(buf, "%d", &session->initial_r2t_en); break; case ISCSI_PARAM_MAX_R2T:
sscanf(buf, "%hu", &session->max_r2t); break; case ISCSI_PARAM_IMM_DATA_EN:
sscanf(buf, "%d", &session->imm_data_en); break; case ISCSI_PARAM_FIRST_BURST:
sscanf(buf, "%d", &session->first_burst); break; case ISCSI_PARAM_MAX_BURST:
sscanf(buf, "%d", &session->max_burst); break; case ISCSI_PARAM_PDU_INORDER_EN:
sscanf(buf, "%d", &session->pdu_inorder_en); break; case ISCSI_PARAM_DATASEQ_INORDER_EN:
sscanf(buf, "%d", &session->dataseq_inorder_en); break; case ISCSI_PARAM_ERL:
sscanf(buf, "%d", &session->erl); break; case ISCSI_PARAM_EXP_STATSN:
sscanf(buf, "%u", &conn->exp_statsn); break; case ISCSI_PARAM_USERNAME: return iscsi_switch_str_param(&session->username, buf); case ISCSI_PARAM_USERNAME_IN: return iscsi_switch_str_param(&session->username_in, buf); case ISCSI_PARAM_PASSWORD: return iscsi_switch_str_param(&session->password, buf); case ISCSI_PARAM_PASSWORD_IN: return iscsi_switch_str_param(&session->password_in, buf); case ISCSI_PARAM_TARGET_NAME: return iscsi_switch_str_param(&session->targetname, buf); case ISCSI_PARAM_TARGET_ALIAS: return iscsi_switch_str_param(&session->targetalias, buf); case ISCSI_PARAM_TPGT:
sscanf(buf, "%d", &session->tpgt); break; case ISCSI_PARAM_PERSISTENT_PORT:
sscanf(buf, "%d", &conn->persistent_port); break; case ISCSI_PARAM_PERSISTENT_ADDRESS: return iscsi_switch_str_param(&conn->persistent_address, buf); case ISCSI_PARAM_IFACE_NAME: return iscsi_switch_str_param(&session->ifacename, buf); case ISCSI_PARAM_INITIATOR_NAME: return iscsi_switch_str_param(&session->initiatorname, buf); case ISCSI_PARAM_BOOT_ROOT: return iscsi_switch_str_param(&session->boot_root, buf); case ISCSI_PARAM_BOOT_NIC: return iscsi_switch_str_param(&session->boot_nic, buf); case ISCSI_PARAM_BOOT_TARGET: return iscsi_switch_str_param(&session->boot_target, buf); case ISCSI_PARAM_PORTAL_TYPE: return iscsi_switch_str_param(&session->portal_type, buf); case ISCSI_PARAM_DISCOVERY_PARENT_TYPE: return iscsi_switch_str_param(&session->discovery_parent_type,
buf); case ISCSI_PARAM_DISCOVERY_SESS:
sscanf(buf, "%d", &val);
session->discovery_sess = !!val; break; case ISCSI_PARAM_LOCAL_IPADDR: return iscsi_switch_str_param(&conn->local_ipaddr, buf); default: return -ENOSYS;
}
return 0;
}
EXPORT_SYMBOL_GPL(iscsi_set_param);
int iscsi_session_get_param(struct iscsi_cls_session *cls_session, enum iscsi_param param, char *buf)
{ struct iscsi_session *session = cls_session->dd_data; int len;
switch(param) { case ISCSI_PARAM_FAST_ABORT:
len = sysfs_emit(buf, "%d\n", session->fast_abort); break; case ISCSI_PARAM_ABORT_TMO:
len = sysfs_emit(buf, "%d\n", session->abort_timeout); break; case ISCSI_PARAM_LU_RESET_TMO:
len = sysfs_emit(buf, "%d\n", session->lu_reset_timeout); break; case ISCSI_PARAM_TGT_RESET_TMO:
len = sysfs_emit(buf, "%d\n", session->tgt_reset_timeout); break; case ISCSI_PARAM_INITIAL_R2T_EN:
len = sysfs_emit(buf, "%d\n", session->initial_r2t_en); break; case ISCSI_PARAM_MAX_R2T:
len = sysfs_emit(buf, "%hu\n", session->max_r2t); break; case ISCSI_PARAM_IMM_DATA_EN:
len = sysfs_emit(buf, "%d\n", session->imm_data_en); break; case ISCSI_PARAM_FIRST_BURST:
len = sysfs_emit(buf, "%u\n", session->first_burst); break; case ISCSI_PARAM_MAX_BURST:
len = sysfs_emit(buf, "%u\n", session->max_burst); break; case ISCSI_PARAM_PDU_INORDER_EN:
len = sysfs_emit(buf, "%d\n", session->pdu_inorder_en); break; case ISCSI_PARAM_DATASEQ_INORDER_EN:
len = sysfs_emit(buf, "%d\n", session->dataseq_inorder_en); break; case ISCSI_PARAM_DEF_TASKMGMT_TMO:
len = sysfs_emit(buf, "%d\n", session->def_taskmgmt_tmo); break; case ISCSI_PARAM_ERL:
len = sysfs_emit(buf, "%d\n", session->erl); break; case ISCSI_PARAM_TARGET_NAME:
len = sysfs_emit(buf, "%s\n", session->targetname); break; case ISCSI_PARAM_TARGET_ALIAS:
len = sysfs_emit(buf, "%s\n", session->targetalias); break; case ISCSI_PARAM_TPGT:
len = sysfs_emit(buf, "%d\n", session->tpgt); break; case ISCSI_PARAM_USERNAME:
len = sysfs_emit(buf, "%s\n", session->username); break; case ISCSI_PARAM_USERNAME_IN:
len = sysfs_emit(buf, "%s\n", session->username_in); break; case ISCSI_PARAM_PASSWORD:
len = sysfs_emit(buf, "%s\n", session->password); break; case ISCSI_PARAM_PASSWORD_IN:
len = sysfs_emit(buf, "%s\n", session->password_in); break; case ISCSI_PARAM_IFACE_NAME:
len = sysfs_emit(buf, "%s\n", session->ifacename); break; case ISCSI_PARAM_INITIATOR_NAME:
len = sysfs_emit(buf, "%s\n", session->initiatorname); break; case ISCSI_PARAM_BOOT_ROOT:
len = sysfs_emit(buf, "%s\n", session->boot_root); break; case ISCSI_PARAM_BOOT_NIC:
len = sysfs_emit(buf, "%s\n", session->boot_nic); break; case ISCSI_PARAM_BOOT_TARGET:
len = sysfs_emit(buf, "%s\n", session->boot_target); break; case ISCSI_PARAM_AUTO_SND_TGT_DISABLE:
len = sysfs_emit(buf, "%u\n", session->auto_snd_tgt_disable); break; case ISCSI_PARAM_DISCOVERY_SESS:
len = sysfs_emit(buf, "%u\n", session->discovery_sess); break; case ISCSI_PARAM_PORTAL_TYPE:
len = sysfs_emit(buf, "%s\n", session->portal_type); break; case ISCSI_PARAM_CHAP_AUTH_EN:
len = sysfs_emit(buf, "%u\n", session->chap_auth_en); break; case ISCSI_PARAM_DISCOVERY_LOGOUT_EN:
len = sysfs_emit(buf, "%u\n", session->discovery_logout_en); break; case ISCSI_PARAM_BIDI_CHAP_EN:
len = sysfs_emit(buf, "%u\n", session->bidi_chap_en); break; case ISCSI_PARAM_DISCOVERY_AUTH_OPTIONAL:
len = sysfs_emit(buf, "%u\n", session->discovery_auth_optional); break; case ISCSI_PARAM_DEF_TIME2WAIT:
len = sysfs_emit(buf, "%d\n", session->time2wait); break; case ISCSI_PARAM_DEF_TIME2RETAIN:
len = sysfs_emit(buf, "%d\n", session->time2retain); break; case ISCSI_PARAM_TSID:
len = sysfs_emit(buf, "%u\n", session->tsid); break; case ISCSI_PARAM_ISID:
len = sysfs_emit(buf, "%02x%02x%02x%02x%02x%02x\n",
session->isid[0], session->isid[1],
session->isid[2], session->isid[3],
session->isid[4], session->isid[5]); break; case ISCSI_PARAM_DISCOVERY_PARENT_IDX:
len = sysfs_emit(buf, "%u\n", session->discovery_parent_idx); break; case ISCSI_PARAM_DISCOVERY_PARENT_TYPE: if (session->discovery_parent_type)
len = sysfs_emit(buf, "%s\n",
session->discovery_parent_type); else
len = sysfs_emit(buf, "\n"); break; default: return -ENOSYS;
}
switch (addr->ss_family) { case AF_INET:
sin = (struct sockaddr_in *)addr; break; case AF_INET6:
sin6 = (struct sockaddr_in6 *)addr; break; default: return -EINVAL;
}
switch (param) { case ISCSI_PARAM_CONN_ADDRESS: case ISCSI_HOST_PARAM_IPADDRESS: if (sin)
len = sysfs_emit(buf, "%pI4\n", &sin->sin_addr.s_addr); else
len = sysfs_emit(buf, "%pI6\n", &sin6->sin6_addr); break; case ISCSI_PARAM_CONN_PORT: case ISCSI_PARAM_LOCAL_PORT: if (sin)
len = sysfs_emit(buf, "%hu\n", be16_to_cpu(sin->sin_port)); else
len = sysfs_emit(buf, "%hu\n",
be16_to_cpu(sin6->sin6_port)); break; default: return -EINVAL;
}
int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf)
{ struct iscsi_conn *conn = cls_conn->dd_data; int len;
switch(param) { case ISCSI_PARAM_PING_TMO:
len = sysfs_emit(buf, "%u\n", conn->ping_timeout); break; case ISCSI_PARAM_RECV_TMO:
len = sysfs_emit(buf, "%u\n", conn->recv_timeout); break; case ISCSI_PARAM_MAX_RECV_DLENGTH:
len = sysfs_emit(buf, "%u\n", conn->max_recv_dlength); break; case ISCSI_PARAM_MAX_XMIT_DLENGTH:
len = sysfs_emit(buf, "%u\n", conn->max_xmit_dlength); break; case ISCSI_PARAM_HDRDGST_EN:
len = sysfs_emit(buf, "%d\n", conn->hdrdgst_en); break; case ISCSI_PARAM_DATADGST_EN:
len = sysfs_emit(buf, "%d\n", conn->datadgst_en); break; case ISCSI_PARAM_IFMARKER_EN:
len = sysfs_emit(buf, "%d\n", conn->ifmarker_en); break; case ISCSI_PARAM_OFMARKER_EN:
len = sysfs_emit(buf, "%d\n", conn->ofmarker_en); break; case ISCSI_PARAM_EXP_STATSN:
len = sysfs_emit(buf, "%u\n", conn->exp_statsn); break; case ISCSI_PARAM_PERSISTENT_PORT:
len = sysfs_emit(buf, "%d\n", conn->persistent_port); break; case ISCSI_PARAM_PERSISTENT_ADDRESS:
len = sysfs_emit(buf, "%s\n", conn->persistent_address); break; case ISCSI_PARAM_STATSN:
len = sysfs_emit(buf, "%u\n", conn->statsn); break; case ISCSI_PARAM_MAX_SEGMENT_SIZE:
len = sysfs_emit(buf, "%u\n", conn->max_segment_size); break; case ISCSI_PARAM_KEEPALIVE_TMO:
len = sysfs_emit(buf, "%u\n", conn->keepalive_tmo); break; case ISCSI_PARAM_LOCAL_PORT:
len = sysfs_emit(buf, "%u\n", conn->local_port); break; case ISCSI_PARAM_TCP_TIMESTAMP_STAT:
len = sysfs_emit(buf, "%u\n", conn->tcp_timestamp_stat); break; case ISCSI_PARAM_TCP_NAGLE_DISABLE:
len = sysfs_emit(buf, "%u\n", conn->tcp_nagle_disable); break; case ISCSI_PARAM_TCP_WSF_DISABLE:
len = sysfs_emit(buf, "%u\n", conn->tcp_wsf_disable); break; case ISCSI_PARAM_TCP_TIMER_SCALE:
len = sysfs_emit(buf, "%u\n", conn->tcp_timer_scale); break; case ISCSI_PARAM_TCP_TIMESTAMP_EN:
len = sysfs_emit(buf, "%u\n", conn->tcp_timestamp_en); break; case ISCSI_PARAM_IP_FRAGMENT_DISABLE:
len = sysfs_emit(buf, "%u\n", conn->fragment_disable); break; case ISCSI_PARAM_IPV4_TOS:
len = sysfs_emit(buf, "%u\n", conn->ipv4_tos); break; case ISCSI_PARAM_IPV6_TC:
len = sysfs_emit(buf, "%u\n", conn->ipv6_traffic_class); break; case ISCSI_PARAM_IPV6_FLOW_LABEL:
len = sysfs_emit(buf, "%u\n", conn->ipv6_flow_label); break; case ISCSI_PARAM_IS_FW_ASSIGNED_IPV6:
len = sysfs_emit(buf, "%u\n", conn->is_fw_assigned_ipv6); break; case ISCSI_PARAM_TCP_XMIT_WSF:
len = sysfs_emit(buf, "%u\n", conn->tcp_xmit_wsf); break; case ISCSI_PARAM_TCP_RECV_WSF:
len = sysfs_emit(buf, "%u\n", conn->tcp_recv_wsf); break; case ISCSI_PARAM_LOCAL_IPADDR:
len = sysfs_emit(buf, "%s\n", conn->local_ipaddr); break; default: return -ENOSYS;
}
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.