blob: e91a885416fb1e5d25d4a73035b171b9e8ce218d [file] [log] [blame]
/*
* Network threads.
*
* Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <net/tcp_states.h>
#ifdef INSIDE_KERNEL_TREE
#include <scst/iscsit_transport.h>
#else
#include "iscsit_transport.h"
#endif
#include "iscsi_trace_flag.h"
#include "iscsi.h"
#include "digest.h"
/* Read data states */
enum rx_state {
RX_INIT_BHS, /* Must be zero for better "switch" optimization. */
RX_BHS,
RX_CMD_START,
RX_DATA,
RX_END,
RX_CMD_CONTINUE,
RX_INIT_HDIGEST,
RX_CHECK_HDIGEST,
RX_INIT_DDIGEST,
RX_CHECK_DDIGEST,
RX_AHS,
RX_PADDING,
};
enum tx_state {
TX_INIT = 0, /* Must be zero for better "switch" optimization. */
TX_BHS_DATA,
TX_INIT_PADDING,
TX_PADDING,
TX_INIT_DDIGEST,
TX_DDIGEST,
TX_END,
};
static void free_pending_commands(struct iscsi_conn *conn)
{
struct iscsi_session *session = conn->session;
struct list_head *pending_list = &session->pending_list;
int req_freed;
struct iscsi_cmnd *cmnd;
spin_lock(&session->sn_lock);
do {
req_freed = 0;
list_for_each_entry(cmnd, pending_list, pending_list_entry) {
TRACE_CONN_CLOSE_DBG("Pending cmd %p (conn %p, cmd_sn %u, exp_cmd_sn %u)",
cmnd, conn, cmnd->pdu.bhs.sn,
session->exp_cmd_sn);
if ((cmnd->conn == conn) &&
(session->exp_cmd_sn == cmnd->pdu.bhs.sn)) {
TRACE_MGMT_DBG("Freeing pending cmd %p (cmd_sn %u, exp_cmd_sn %u)",
cmnd, cmnd->pdu.bhs.sn,
session->exp_cmd_sn);
list_del(&cmnd->pending_list_entry);
cmnd->pending = 0;
session->exp_cmd_sn++;
spin_unlock(&session->sn_lock);
req_cmnd_release_force(cmnd);
req_freed = 1;
spin_lock(&session->sn_lock);
break;
}
}
} while (req_freed);
spin_unlock(&session->sn_lock);
return;
}
static void free_orphaned_pending_commands(struct iscsi_conn *conn)
{
struct iscsi_session *session = conn->session;
struct list_head *pending_list = &session->pending_list;
int req_freed;
struct iscsi_cmnd *cmnd;
spin_lock(&session->sn_lock);
do {
req_freed = 0;
list_for_each_entry(cmnd, pending_list, pending_list_entry) {
TRACE_CONN_CLOSE_DBG("Pending cmd %p (conn %p, cmd_sn %u, exp_cmd_sn %u)",
cmnd, conn, cmnd->pdu.bhs.sn,
session->exp_cmd_sn);
if (cmnd->conn == conn) {
TRACE_MGMT_DBG("Freeing orphaned pending cmnd %p (cmd_sn %u, exp_cmd_sn %u)",
cmnd, cmnd->pdu.bhs.sn,
session->exp_cmd_sn);
list_del(&cmnd->pending_list_entry);
cmnd->pending = 0;
if (session->exp_cmd_sn == cmnd->pdu.bhs.sn)
session->exp_cmd_sn++;
spin_unlock(&session->sn_lock);
req_cmnd_release_force(cmnd);
req_freed = 1;
spin_lock(&session->sn_lock);
break;
}
}
} while (req_freed);
spin_unlock(&session->sn_lock);
return;
}
#ifdef CONFIG_SCST_DEBUG
static void trace_conn_close(struct iscsi_conn *conn)
{
struct iscsi_cmnd *cmnd;
#if 0
if (time_after(jiffies, start_waiting + 10*HZ))
trace_flag |= TRACE_CONN_OC_DBG;
#endif
spin_lock_bh(&conn->cmd_list_lock);
list_for_each_entry(cmnd, &conn->cmd_list,
cmd_list_entry) {
TRACE_CONN_CLOSE_DBG(
"cmd %p, scst_cmd %p, scst_state %x, scst_cmd state %d, r2t_len_to_receive %d, ref_cnt %d, sn %u, parent_req %p, pending %d",
cmnd, cmnd->scst_cmd, cmnd->scst_state,
((cmnd->parent_req == NULL) && cmnd->scst_cmd) ?
cmnd->scst_cmd->state : -1,
cmnd->r2t_len_to_receive, atomic_read(&cmnd->ref_cnt),
cmnd->pdu.bhs.sn, cmnd->parent_req, cmnd->pending);
}
spin_unlock_bh(&conn->cmd_list_lock);
return;
}
#else /* CONFIG_SCST_DEBUG */
static void trace_conn_close(struct iscsi_conn *conn) {}
#endif /* CONFIG_SCST_DEBUG */
void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd)
{
int fn = scst_mgmt_cmd_get_fn(scst_mcmd);
void *priv = scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
TRACE_MGMT_DBG("scst_mcmd %p, fn %d, priv %p", scst_mcmd, fn, priv);
switch (fn) {
case SCST_NEXUS_LOSS_SESS:
{
struct iscsi_conn *conn = priv;
struct iscsi_session *sess = conn->session;
struct iscsi_conn *c;
if (sess->sess_reinst_successor != NULL)
scst_reassign_retained_sess_states(
sess->sess_reinst_successor->scst_sess,
sess->scst_sess);
mutex_lock(&sess->target->target_mutex);
/*
* We can't mark sess as shutting down earlier, because until
* now it might have pending commands. Otherwise, in case of
* reinstatement, it might lead to data corruption, because
* commands in being reinstated session can be executed
* after commands in the new session.
*/
sess->sess_shutting_down = 1;
list_for_each_entry(c, &sess->conn_list, conn_list_entry) {
if (!test_bit(ISCSI_CONN_SHUTTINGDOWN,
&c->conn_aflags)) {
sess->sess_shutting_down = 0;
break;
}
}
if (conn->conn_reinst_successor != NULL) {
sBUG_ON(!test_bit(ISCSI_CONN_REINSTATING,
&conn->conn_reinst_successor->conn_aflags));
conn_reinst_finished(conn->conn_reinst_successor);
conn->conn_reinst_successor = NULL;
} else if (sess->sess_reinst_successor != NULL) {
sess_reinst_finished(sess->sess_reinst_successor);
sess->sess_reinst_successor = NULL;
}
mutex_unlock(&sess->target->target_mutex);
complete_all(&conn->ready_to_free);
break;
}
case SCST_ABORT_ALL_TASKS_SESS:
case SCST_ABORT_ALL_TASKS:
case SCST_NEXUS_LOSS:
sBUG();
break;
default:
/* Nothing to do */
break;
}
return;
}
/* No locks */
static void close_conn(struct iscsi_conn *conn)
{
struct iscsi_session *session = conn->session;
struct iscsi_target *target = conn->target;
typeof(jiffies) start_waiting = jiffies;
typeof(jiffies) shut_start_waiting = start_waiting;
bool pending_reported = 0, wait_expired = 0, shut_expired = 0;
uint32_t tid, cid;
uint64_t sid;
int rc;
int lun = 0;
#define CONN_PENDING_TIMEOUT ((typeof(jiffies))10*HZ)
#define CONN_WAIT_TIMEOUT ((typeof(jiffies))10*HZ)
#define CONN_REG_SHUT_TIMEOUT ((typeof(jiffies))125*HZ)
#define CONN_DEL_SHUT_TIMEOUT ((typeof(jiffies))10*HZ)
TRACE_ENTRY();
TRACE_MGMT_DBG("Closing connection %p (conn_ref_cnt=%d)", conn,
atomic_read(&conn->conn_ref_cnt));
iscsi_extracheck_is_rd_thread(conn);
sBUG_ON(!conn->closing);
if (conn->active_close) {
/* We want all our already send operations to complete */
conn->transport->iscsit_conn_close(conn, RCV_SHUTDOWN);
} else {
conn->transport->iscsit_conn_close(conn,
RCV_SHUTDOWN|SEND_SHUTDOWN);
}
mutex_lock(&session->target->target_mutex);
set_bit(ISCSI_CONN_SHUTTINGDOWN, &conn->conn_aflags);
mutex_unlock(&session->target->target_mutex);
rc = scst_rx_mgmt_fn_lun(session->scst_sess,
SCST_NEXUS_LOSS_SESS, &lun, sizeof(lun),
SCST_NON_ATOMIC, conn);
if (rc != 0)
PRINT_ERROR("SCST_NEXUS_LOSS_SESS failed %d", rc);
if (conn->read_state != RX_INIT_BHS) {
struct iscsi_cmnd *cmnd = conn->read_cmnd;
if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
TRACE_CONN_CLOSE_DBG("Going to wait for cmnd %p to change state from RX_CMD",
cmnd);
}
wait_event(conn->read_state_waitQ,
cmnd->scst_state != ISCSI_CMD_STATE_RX_CMD);
TRACE_CONN_CLOSE_DBG("Releasing conn->read_cmnd %p (conn %p)",
conn->read_cmnd, conn);
conn->read_cmnd = NULL;
conn->read_state = RX_INIT_BHS;
req_cmnd_release_force(cmnd);
}
conn_abort(conn);
/* ToDo: not the best way to wait */
while (atomic_read(&conn->conn_ref_cnt) != 0) {
if (conn->conn_tm_active)
iscsi_check_tm_data_wait_timeouts(conn, true);
mutex_lock(&target->target_mutex);
spin_lock(&session->sn_lock);
if (session->tm_rsp && session->tm_rsp->conn == conn) {
struct iscsi_cmnd *tm_rsp = session->tm_rsp;
iscsi_drop_delayed_tm_rsp(tm_rsp);
spin_unlock(&session->sn_lock);
mutex_unlock(&target->target_mutex);
rsp_cmnd_release(tm_rsp);
} else {
spin_unlock(&session->sn_lock);
mutex_unlock(&target->target_mutex);
}
/* It's safe to check it without sn_lock */
if (!list_empty(&session->pending_list)) {
TRACE_CONN_CLOSE_DBG("Disposing pending commands on connection %p (conn_ref_cnt=%d)",
conn, atomic_read(&conn->conn_ref_cnt));
free_pending_commands(conn);
if (time_after(jiffies,
start_waiting + CONN_PENDING_TIMEOUT)) {
if (!pending_reported) {
TRACE_CONN_CLOSE("%s",
"Pending wait time expired");
pending_reported = 1;
}
free_orphaned_pending_commands(conn);
}
}
conn->transport->iscsit_make_conn_wr_active(conn);
/* That's for active close only, actually */
if (time_after(jiffies, start_waiting + CONN_WAIT_TIMEOUT) &&
!wait_expired) {
TRACE_CONN_CLOSE("Wait time expired (conn %p)", conn);
conn->transport->iscsit_conn_close(conn, SEND_SHUTDOWN);
wait_expired = 1;
shut_start_waiting = jiffies;
}
if (wait_expired && !shut_expired &&
time_after(jiffies, shut_start_waiting +
conn->deleting ? CONN_DEL_SHUT_TIMEOUT :
CONN_REG_SHUT_TIMEOUT)) {
TRACE_CONN_CLOSE("Wait time after shutdown expired (conn %p)",
conn);
conn->transport->iscsit_conn_close(conn, 0);
shut_expired = 1;
}
if (conn->deleting)
msleep(200);
else
msleep(1000);
TRACE_CONN_CLOSE_DBG("conn %p, conn_ref_cnt %d left, wr_state %d, exp_cmd_sn %u",
conn, atomic_read(&conn->conn_ref_cnt),
conn->wr_state, session->exp_cmd_sn);
trace_conn_close(conn);
if (!conn->session->sess_params.rdma_extensions) {
/* It might never be called for being closed conn */
__iscsi_write_space_ready(conn);
}
}
if (!conn->session->sess_params.rdma_extensions) {
write_lock_bh(&conn->sock->sk->sk_callback_lock);
conn->sock->sk->sk_state_change = conn->old_state_change;
conn->sock->sk->sk_data_ready = conn->old_data_ready;
conn->sock->sk->sk_write_space = conn->old_write_space;
write_unlock_bh(&conn->sock->sk->sk_callback_lock);
}
while (1) {
bool t;
spin_lock_bh(&conn->conn_thr_pool->wr_lock);
t = (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE);
spin_unlock_bh(&conn->conn_thr_pool->wr_lock);
if (t && (atomic_read(&conn->conn_ref_cnt) == 0))
break;
TRACE_CONN_CLOSE_DBG("Waiting for wr thread (conn %p), wr_state %x",
conn, conn->wr_state);
msleep(50);
}
wait_for_completion(&conn->ready_to_free);
tid = target->tid;
sid = session->sid;
cid = conn->cid;
mutex_lock(&target->target_mutex);
conn_free(conn);
mutex_unlock(&target->target_mutex);
/*
* We can't send E_CONN_CLOSE earlier, because otherwise we would have
* a race, when the user space tried to destroy session, which still
* has connections.
*
* !! All target, session and conn can be already dead here !!
*/
TRACE_CONN_CLOSE("Notifying user space about closing connection %p",
conn);
event_send(tid, sid, cid, 0, E_CONN_CLOSE, NULL, NULL);
TRACE_EXIT();
return;
}
static int close_conn_thr(void *arg)
{
struct iscsi_conn *conn = arg;
TRACE_ENTRY();
#ifdef CONFIG_SCST_EXTRACHECKS
/*
* To satisfy iscsi_extracheck_is_rd_thread() in functions called
* on the connection close. It is safe, because at this point conn
* can't be used by any other thread.
*/
conn->rd_task = current;
#endif
close_conn(conn);
TRACE_EXIT();
return 0;
}
/* No locks */
void start_close_conn(struct iscsi_conn *conn)
{
struct task_struct *t;
TRACE_ENTRY();
t = kthread_run(close_conn_thr, conn, "iscsi_conn_cleanup");
if (IS_ERR(t)) {
PRINT_ERROR("kthread_run() failed (%ld), closing conn %p directly",
PTR_ERR(t), conn);
close_conn(conn);
}
TRACE_EXIT();
return;
}
EXPORT_SYMBOL(start_close_conn);
static inline void iscsi_conn_init_read(struct iscsi_conn *conn,
void *data, size_t len)
{
conn->read_iov[0].iov_base = data;
conn->read_iov[0].iov_len = len;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
iov_iter_kvec(&conn->read_msg.msg_iter, READ, conn->read_iov, 1, len);
#else
conn->read_msg.msg_iov = conn->read_iov;
conn->read_msg.msg_iovlen = 1;
conn->read_size = len;
#endif
return;
}
static void iscsi_conn_prepare_read_ahs(struct iscsi_conn *conn,
struct iscsi_cmnd *cmnd)
{
int asize = (cmnd->pdu.ahssize + 3) & -4;
/* ToDo: __GFP_NOFAIL ?? */
cmnd->pdu.ahs = kmalloc(asize, __GFP_NOFAIL|GFP_KERNEL);
sBUG_ON(cmnd->pdu.ahs == NULL);
iscsi_conn_init_read(conn, cmnd->pdu.ahs, asize);
return;
}
struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn)
{
struct iscsi_cmnd *cmnd = NULL;
spin_lock_bh(&conn->write_list_lock);
if (!list_empty(&conn->write_list)) {
cmnd = list_first_entry(&conn->write_list, struct iscsi_cmnd,
write_list_entry);
cmd_del_from_write_list(cmnd);
cmnd->write_processing_started = 1;
} else {
spin_unlock_bh(&conn->write_list_lock);
goto out;
}
spin_unlock_bh(&conn->write_list_lock);
if (unlikely(test_bit(ISCSI_CMD_ABORTED,
&cmnd->parent_req->prelim_compl_flags))) {
TRACE_MGMT_DBG("Going to send acmd %p (scst cmd %p, state %d, parent_req %p)",
cmnd, cmnd->scst_cmd, cmnd->scst_state,
cmnd->parent_req);
}
if (unlikely(cmnd_opcode(cmnd) == ISCSI_OP_SCSI_TASK_MGT_RSP)) {
#ifdef CONFIG_SCST_DEBUG
struct iscsi_task_mgt_hdr *req_hdr =
(struct iscsi_task_mgt_hdr *)&cmnd->parent_req->pdu.bhs;
struct iscsi_task_rsp_hdr *rsp_hdr =
(struct iscsi_task_rsp_hdr *)&cmnd->pdu.bhs;
TRACE_MGMT_DBG("Going to send TM response %p (status %d, "
"fn %d, parent_req %p)", cmnd, rsp_hdr->response,
req_hdr->function & ISCSI_FUNCTION_MASK,
cmnd->parent_req);
#endif
}
out:
return cmnd;
}
EXPORT_SYMBOL(iscsi_get_send_cmnd);
/* Returns number of bytes left to receive or <0 for error */
static int do_recv(struct iscsi_conn *conn)
{
int res;
mm_segment_t oldfs;
struct msghdr *msg;
int read_size;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
struct iovec *first_iov;
int first_len;
#endif
EXTRACHECKS_BUG_ON(conn->read_cmnd == NULL);
if (unlikely(conn->closing)) {
res = -EIO;
goto out;
}
/*
* We suppose that if sock_recvmsg() returned less data than requested,
* then next time it will return -EAGAIN, so there's no point to call
* it again.
*/
restart:
msg = &conn->read_msg;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
read_size = msg->msg_iter.count;
#else
read_size = conn->read_size;
first_iov = msg->msg_iov;
first_len = first_iov->iov_len;
#endif
oldfs = get_fs();
set_fs(KERNEL_DS);
res = sock_recvmsg(conn->sock, msg,
#if SOCK_RECVMSG_HAS_FOUR_ARGS
read_size,
#endif
MSG_DONTWAIT | MSG_NOSIGNAL);
set_fs(oldfs);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
TRACE_DBG("nr_segs %ld, bytes_left %zd, res %d",
msg->msg_iter.nr_segs, msg->msg_iter.count, res);
#else
TRACE_DBG("msg_iovlen %zd, read_size %d, res %d", msg->msg_iovlen,
read_size, res);
#endif
if (res > 0) {
/*
* To save CPU cycles we suppose that sock_recvmsg() adjusts
* msg->msg_iov and msg->msg_iovlen. The BUG_ON() statement
* below verifies this.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
sBUG_ON(msg->msg_iter.count + res != read_size);
res = msg->msg_iter.count;
#else
sBUG_ON((res >= first_len) && (first_iov->iov_len != 0));
conn->read_size -= res;
res = conn->read_size;
#endif
} else {
switch (res) {
case -EAGAIN:
TRACE_DBG("EAGAIN received for conn %p", conn);
res = read_size;
break;
case -ERESTARTSYS:
TRACE_DBG("ERESTARTSYS received for conn %p", conn);
goto restart;
default:
if (!conn->closing) {
PRINT_ERROR("sock_recvmsg() failed: %d (conn %p)",
res, conn);
mark_conn_closed(conn);
}
if (res == 0)
res = -EIO;
break;
}
}
out:
TRACE_EXIT_RES(res);
return res;
}
static int iscsi_rx_check_ddigest(struct iscsi_conn *conn)
{
struct iscsi_cmnd *cmnd = conn->read_cmnd;
int res;
res = do_recv(conn);
if (res == 0) {
conn->read_state = RX_END;
if (cmnd->pdu.datasize <= 16*1024) {
/*
* It's cache hot, so let's compute it inline. The
* choice here about what will expose more latency:
* possible cache misses or the digest calculation.
*/
TRACE_DBG("cmnd %p, opcode %x: checking RX ddigest inline",
cmnd, cmnd_opcode(cmnd));
cmnd->ddigest_checked = 1;
res = digest_rx_data(cmnd);
if (unlikely(res != 0)) {
struct iscsi_cmnd *orig_req;
if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_DATA_OUT)
orig_req = cmnd->cmd_req;
else
orig_req = cmnd;
if (unlikely(orig_req->scst_cmd == NULL)) {
/* Just drop it */
iscsi_preliminary_complete(cmnd,
orig_req,
false);
} else {
set_scst_preliminary_status_rsp(orig_req, false,
SCST_LOAD_SENSE(iscsi_sense_crc_error));
/*
* Let's prelim complete cmnd too to
* handle the DATA OUT case
*/
iscsi_preliminary_complete(cmnd,
orig_req,
false);
}
res = 0;
}
} else if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) {
cmd_add_on_rx_ddigest_list(cmnd, cmnd);
cmnd_get(cmnd);
} else if (cmnd_opcode(cmnd) != ISCSI_OP_SCSI_DATA_OUT) {
/*
* We could get here only for Nop-Out. ISCSI RFC
* doesn't specify how to deal with digest errors in
* this case. Let's just drop the command.
*/
TRACE_DBG("cmnd %p, opcode %x: checking NOP RX ddigest",
cmnd, cmnd_opcode(cmnd));
res = digest_rx_data(cmnd);
if (unlikely(res != 0)) {
iscsi_preliminary_complete(cmnd, cmnd, false);
res = 0;
}
}
}
return res;
}
/* No locks, conn is rd processing */
static int process_read_io(struct iscsi_conn *conn, int *closed)
{
struct iscsi_cmnd *cmnd = conn->read_cmnd;
int bytes_left, res;
TRACE_ENTRY();
/* In case of error cmnd will be freed in close_conn() */
do {
switch (conn->read_state) {
case RX_INIT_BHS:
EXTRACHECKS_BUG_ON(conn->read_cmnd != NULL);
cmnd = conn->transport->iscsit_alloc_cmd(conn, NULL);
conn->read_cmnd = cmnd;
iscsi_conn_init_read(cmnd->conn, &cmnd->pdu.bhs,
sizeof(cmnd->pdu.bhs));
conn->read_state = RX_BHS;
fallthrough;
case RX_BHS:
res = do_recv(conn);
if (res == 0) {
/*
* This command not yet received on the
* aborted time, so shouldn't be affected by
* any abort. It should not be affected by
* conn_abort() as well, because close
* connection is initiated from single (this)
* read thread, so conn_abort() call stack can
* not be initiated in parallel to receive all
* the data event (do_recv() has check of
* conn->closing in the beginning)
*/
EXTRACHECKS_BUG_ON(cmnd->prelim_compl_flags != 0);
iscsi_cmnd_get_length(&cmnd->pdu);
if (cmnd->pdu.ahssize == 0) {
if ((conn->hdigest_type & DIGEST_NONE) == 0)
conn->read_state = RX_INIT_HDIGEST;
else
conn->read_state = RX_CMD_START;
} else {
iscsi_conn_prepare_read_ahs(conn, cmnd);
conn->read_state = RX_AHS;
}
}
break;
case RX_CMD_START:
res = cmnd_rx_start(cmnd);
if (res == 0) {
if (cmnd->pdu.datasize == 0)
conn->read_state = RX_END;
else
conn->read_state = RX_DATA;
} else if (res > 0)
conn->read_state = RX_CMD_CONTINUE;
else
sBUG_ON(!conn->closing);
break;
case RX_CMD_CONTINUE:
if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
TRACE_DBG("cmnd %p is still in RX_CMD state",
cmnd);
res = 1;
break;
}
res = cmnd_rx_continue(cmnd);
if (unlikely(res != 0))
sBUG_ON(!conn->closing);
else {
if (cmnd->pdu.datasize == 0)
conn->read_state = RX_END;
else
conn->read_state = RX_DATA;
}
break;
case RX_DATA:
res = do_recv(conn);
if (res == 0) {
int psz = ((cmnd->pdu.datasize + 3) & -4) -
cmnd->pdu.datasize;
if (psz != 0) {
TRACE_DBG("padding %d bytes", psz);
iscsi_conn_init_read(conn,
&conn->rpadding, psz);
conn->read_state = RX_PADDING;
} else if ((conn->ddigest_type & DIGEST_NONE) != 0)
conn->read_state = RX_END;
else
conn->read_state = RX_INIT_DDIGEST;
}
break;
case RX_END:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
bytes_left = conn->read_msg.msg_iter.count;
#else
bytes_left = conn->read_size;
#endif
if (unlikely(bytes_left != 0)) {
PRINT_CRIT_ERROR("conn read_size !=0 on RX_END (conn %p, op %x, read_size %d)",
conn, cmnd_opcode(cmnd),
bytes_left);
sBUG();
}
conn->read_cmnd = NULL;
conn->read_state = RX_INIT_BHS;
cmnd_rx_end(cmnd);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
EXTRACHECKS_BUG_ON(conn->read_msg.msg_iter.count != 0);
#else
EXTRACHECKS_BUG_ON(conn->read_size != 0);
#endif
/*
* To maintain fairness. Res must be 0 here anyway, the
* assignment is only to remove compiler warning about
* uninitialized variable.
*/
res = 0;
goto out;
case RX_INIT_HDIGEST:
iscsi_conn_init_read(conn, &cmnd->hdigest, sizeof(u32));
conn->read_state = RX_CHECK_HDIGEST;
fallthrough;
case RX_CHECK_HDIGEST:
res = do_recv(conn);
if (res == 0) {
res = digest_rx_header(cmnd);
if (unlikely(res != 0)) {
PRINT_ERROR("rx header digest for initiator %s (conn %p) failed (%d)",
conn->session->initiator_name,
conn, res);
mark_conn_closed(conn);
} else
conn->read_state = RX_CMD_START;
}
break;
case RX_INIT_DDIGEST:
iscsi_conn_init_read(conn, &cmnd->ddigest, sizeof(u32));
conn->read_state = RX_CHECK_DDIGEST;
fallthrough;
case RX_CHECK_DDIGEST:
res = iscsi_rx_check_ddigest(conn);
break;
case RX_AHS:
res = do_recv(conn);
if (res == 0) {
if ((conn->hdigest_type & DIGEST_NONE) == 0)
conn->read_state = RX_INIT_HDIGEST;
else
conn->read_state = RX_CMD_START;
}
break;
case RX_PADDING:
res = do_recv(conn);
if (res == 0) {
if ((conn->ddigest_type & DIGEST_NONE) == 0)
conn->read_state = RX_INIT_DDIGEST;
else
conn->read_state = RX_END;
}
break;
default:
PRINT_CRIT_ERROR("%d %x", conn->read_state,
cmnd_opcode(cmnd));
res = -1; /* to keep compiler happy */
sBUG();
}
} while (res == 0);
if (unlikely(conn->closing)) {
start_close_conn(conn);
*closed = 1;
}
out:
TRACE_EXIT_RES(res);
return res;
}
/*
* Called under rd_lock and BHs disabled, but will drop it inside,
* then reacquire.
*/
static void scst_do_job_rd(struct iscsi_thread_pool *p)
__acquires(&rd_lock)
__releases(&rd_lock)
{
TRACE_ENTRY();
/*
* We delete/add to tail connections to maintain fairness between them.
*/
while (!list_empty(&p->rd_list)) {
int closed = 0, rc;
struct iscsi_conn *conn = list_first_entry(&p->rd_list,
typeof(*conn), rd_list_entry);
list_del(&conn->rd_list_entry);
sBUG_ON(conn->rd_state == ISCSI_CONN_RD_STATE_PROCESSING);
conn->rd_data_ready = 0;
conn->rd_state = ISCSI_CONN_RD_STATE_PROCESSING;
#ifdef CONFIG_SCST_EXTRACHECKS
conn->rd_task = current;
#endif
spin_unlock_bh(&p->rd_lock);
rc = process_read_io(conn, &closed);
spin_lock_bh(&p->rd_lock);
if (unlikely(closed))
continue;
if (unlikely(conn->conn_tm_active)) {
spin_unlock_bh(&p->rd_lock);
iscsi_check_tm_data_wait_timeouts(conn, false);
spin_lock_bh(&p->rd_lock);
}
#ifdef CONFIG_SCST_EXTRACHECKS
conn->rd_task = NULL;
#endif
if ((rc == 0) || conn->rd_data_ready) {
list_add_tail(&conn->rd_list_entry, &p->rd_list);
conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
} else
conn->rd_state = ISCSI_CONN_RD_STATE_IDLE;
}
TRACE_EXIT();
return;
}
static inline int test_rd_list(struct iscsi_thread_pool *p)
{
int res = !list_empty(&p->rd_list) ||
unlikely(kthread_should_stop());
return res;
}
int istrd(void *arg)
{
struct iscsi_thread_pool *p = arg;
int rc;
TRACE_ENTRY();
PRINT_INFO("Read thread for pool %p started", p);
current->flags |= PF_NOFREEZE;
rc = set_cpus_allowed_ptr(current, &p->cpu_mask);
if (rc != 0)
PRINT_ERROR("Setting CPU affinity failed: %d", rc);
spin_lock_bh(&p->rd_lock);
while (!kthread_should_stop()) {
wait_event_locked(p->rd_waitQ, test_rd_list(p), lock_bh,
p->rd_lock);
scst_do_job_rd(p);
}
spin_unlock_bh(&p->rd_lock);
/*
* If kthread_should_stop() is true, we are guaranteed to be
* on the module unload, so rd_list must be empty.
*/
sBUG_ON(!list_empty(&p->rd_list));
PRINT_INFO("Read thread for pool %p finished", p);
TRACE_EXIT();
return 0;
}
void req_add_to_write_timeout_list(struct iscsi_cmnd *req)
{
struct iscsi_conn *conn;
bool set_conn_tm_active = false;
TRACE_ENTRY();
if (req->on_write_timeout_list)
goto out;
conn = req->conn;
TRACE_DBG("Adding req %p to conn %p write_timeout_list",
req, conn);
spin_lock_bh(&conn->write_list_lock);
/* Recheck, since it can be changed behind us */
if (unlikely(req->on_write_timeout_list)) {
spin_unlock_bh(&conn->write_list_lock);
goto out;
}
req->on_write_timeout_list = 1;
req->write_start = jiffies;
if (unlikely(cmnd_opcode(req) == ISCSI_OP_NOP_OUT)) {
unsigned long req_tt = iscsi_get_timeout_time(req);
struct iscsi_cmnd *r;
bool inserted = false;
list_for_each_entry(r, &conn->write_timeout_list,
write_timeout_list_entry) {
unsigned long tt = iscsi_get_timeout_time(r);
if (time_after(tt, req_tt)) {
TRACE_DBG("Add NOP IN req %p (tt %ld) before req %p (tt %ld)",
req, req_tt, r, tt);
list_add_tail(&req->write_timeout_list_entry,
&r->write_timeout_list_entry);
inserted = true;
break;
}
TRACE_DBG("Skipping op %x req %p (tt %ld)",
cmnd_opcode(r), r, tt);
}
if (!inserted) {
TRACE_DBG("Add NOP IN req %p in the tail", req);
list_add_tail(&req->write_timeout_list_entry,
&conn->write_timeout_list);
}
/* We suppose that nop_in_timeout must be <= data_rsp_timeout */
req_tt += ISCSI_ADD_SCHED_TIME;
if (timer_pending(&conn->rsp_timer) &&
time_after(conn->rsp_timer.expires, req_tt)) {
TRACE_DBG("Timer adjusted for sooner expired NOP IN req %p",
req);
mod_timer(&conn->rsp_timer, req_tt);
}
} else
list_add_tail(&req->write_timeout_list_entry,
&conn->write_timeout_list);
if (!timer_pending(&conn->rsp_timer)) {
unsigned long timeout_time;
if (unlikely(conn->conn_tm_active ||
test_bit(ISCSI_CMD_ABORTED,
&req->prelim_compl_flags))) {
set_conn_tm_active = true;
timeout_time = req->write_start +
ISCSI_TM_DATA_WAIT_TIMEOUT;
} else
timeout_time = iscsi_get_timeout_time(req);
timeout_time += ISCSI_ADD_SCHED_TIME;
TRACE_DBG("Starting timer on %ld (con %p, write_start %ld)",
timeout_time, conn, req->write_start);
conn->rsp_timer.expires = timeout_time;
add_timer(&conn->rsp_timer);
} else if (unlikely(test_bit(ISCSI_CMD_ABORTED,
&req->prelim_compl_flags))) {
unsigned long timeout_time = jiffies +
ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME;
set_conn_tm_active = true;
if (time_after(conn->rsp_timer.expires, timeout_time)) {
TRACE_MGMT_DBG("Mod timer on %ld (conn %p)",
timeout_time, conn);
mod_timer(&conn->rsp_timer, timeout_time);
}
}
spin_unlock_bh(&conn->write_list_lock);
/*
* conn_tm_active can be already cleared by
* iscsi_check_tm_data_wait_timeouts(). write_list_lock is an inner
* lock for rd_lock.
*/
if (unlikely(set_conn_tm_active)) {
spin_lock_bh(&conn->conn_thr_pool->rd_lock);
TRACE_MGMT_DBG("Setting conn_tm_active for conn %p", conn);
conn->conn_tm_active = 1;
spin_unlock_bh(&conn->conn_thr_pool->rd_lock);
}
out:
TRACE_EXIT();
return;
}
static int write_data(struct iscsi_conn *conn)
{
struct file *file;
struct kvec *iop;
struct socket *sock;
ssize_t (*sock_sendpage)(struct socket *, struct page *, int, size_t,
int);
ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int);
struct iscsi_cmnd *write_cmnd = conn->write_cmnd;
struct iscsi_cmnd *ref_cmd;
struct page *page;
struct scatterlist *sg;
int saved_size, size, sendsize;
int length, offset, idx;
int flags, res, count, sg_size;
bool ref_cmd_to_parent;
TRACE_ENTRY();
iscsi_extracheck_is_wr_thread(conn);
if (!write_cmnd->own_sg) {
ref_cmd = write_cmnd->parent_req;
ref_cmd_to_parent = true;
} else {
ref_cmd = write_cmnd;
ref_cmd_to_parent = false;
}
req_add_to_write_timeout_list(write_cmnd->parent_req);
file = conn->file;
size = conn->write_size;
saved_size = size;
iop = conn->write_iop;
count = conn->write_iop_used;
if (iop) {
while (1) {
loff_t off = 0;
int rest;
sBUG_ON(count > ARRAY_SIZE(conn->write_iov));
retry:
res = scst_writev(file, iop, count, &off);
TRACE_WRITE("sid %#Lx, cid %u, res %d, iov_len %zd",
(unsigned long long)conn->session->sid,
conn->cid, res, iop->iov_len);
if (unlikely(res <= 0)) {
if (res == -EAGAIN) {
conn->write_iop = iop;
conn->write_iop_used = count;
goto out_iov;
} else if (res == -EINTR)
goto retry;
goto out_err;
}
rest = res;
size -= res;
while ((typeof(rest))iop->iov_len <= rest && rest) {
rest -= iop->iov_len;
iop++;
count--;
}
if (count == 0) {
conn->write_iop = NULL;
conn->write_iop_used = 0;
if (size)
break;
goto out_iov;
}
sBUG_ON(iop >
conn->write_iov + ARRAY_SIZE(conn->write_iov));
iop->iov_base += rest;
iop->iov_len -= rest;
}
}
sg = write_cmnd->sg;
if (unlikely(sg == NULL)) {
PRINT_INFO("WARNING: Data missed (cmd %p)!", write_cmnd);
res = 0;
goto out;
}
sock = conn->sock;
if (write_cmnd->parent_req->scst_cmd &&
write_cmnd->parent_req->scst_state != ISCSI_CMD_STATE_AEN &&
scst_cmd_get_dh_data_buff_alloced(write_cmnd->parent_req->scst_cmd))
sock_sendpage = sock_no_sendpage;
else
sock_sendpage = sock->ops->sendpage;
flags = MSG_DONTWAIT;
sg_size = size;
if (sg != write_cmnd->rsp_sg) {
/*
* Data scatterlist. It is assumed that only the first element
* has a non-zero offset and that all elements except the
* first and the last have a length that is equal to
* PAGE_SIZE.
*/
offset = conn->write_offset + sg[0].offset;
idx = offset >> PAGE_SHIFT;
offset &= ~PAGE_MASK;
length = min(size, (int)PAGE_SIZE - offset);
TRACE_WRITE("write_offset %d, sg_size %d, idx %d, offset %d, length %d",
conn->write_offset, sg_size, idx, offset, length);
} else {
/*
* Response scatterlist. No assumptions are made about the
* offset nor about the length of scatterlist elements.
*/
idx = 0;
offset = conn->write_offset;
while (offset >= sg[idx].length) {
offset -= sg[idx].length;
idx++;
}
length = sg[idx].length - offset;
offset += sg[idx].offset;
sock_sendpage = sock_no_sendpage;
TRACE_WRITE("rsp_sg: write_offset %d, sg_size %d, idx %d, "
"offset %d, length %d", conn->write_offset, sg_size,
idx, offset, length);
}
page = sg_page(&sg[idx]);
while (1) {
sendpage = sock_sendpage;
sendsize = min(size, length);
if (size <= sendsize) {
retry2:
res = sendpage(sock, page, offset, size, flags);
TRACE_WRITE("Final %s sid %#Lx, cid %u, res %d (page index %lu, offset %u, size %u, cmd %p, page %p)",
(sendpage != sock_no_sendpage) ?
"sendpage" : "sock_no_sendpage",
(unsigned long long)conn->session->sid,
conn->cid, res, page->index,
offset, size, write_cmnd, page);
if (unlikely(res <= 0)) {
if (res == -EINTR)
goto retry2;
else
goto out_res;
}
if (res == size) {
conn->write_size = 0;
res = saved_size;
goto out;
}
offset += res;
size -= res;
goto retry2;
}
retry1:
res = sendpage(sock, page, offset, sendsize, flags | MSG_MORE);
TRACE_WRITE("%s sid %#Lx, cid %u, res %d (page index %lu, offset %u, sendsize %u, size %u, cmd %p, page %p)",
(sendpage != sock_no_sendpage) ? "sendpage" :
"sock_no_sendpage",
(unsigned long long)conn->session->sid, conn->cid,
res, page->index, offset, sendsize, size,
write_cmnd, page);
if (unlikely(res <= 0)) {
if (res == -EINTR)
goto retry1;
else
goto out_res;
}
size -= res;
if (res == sendsize) {
idx++;
EXTRACHECKS_BUG_ON(idx >= ref_cmd->sg_cnt);
page = sg_page(&sg[idx]);
length = sg[idx].length;
offset = sg[idx].offset;
} else {
offset += res;
sendsize -= res;
goto retry1;
}
}
out_off:
conn->write_offset += sg_size - size;
out_iov:
conn->write_size = size;
if ((saved_size == size) && res == -EAGAIN)
goto out;
res = saved_size - size;
out:
TRACE_EXIT_RES(res);
return res;
out_res:
if (res == -EAGAIN)
goto out_off;
/* else go through */
out_err:
#ifndef CONFIG_SCST_DEBUG
if (!conn->closing) {
#else
{
#endif
PRINT_ERROR("error %d at sid:cid %#Lx:%u, cmnd %p", res,
(unsigned long long)conn->session->sid,
conn->cid, conn->write_cmnd);
}
if (ref_cmd_to_parent) {
if (ref_cmd->scst_state == ISCSI_CMD_STATE_AEN) {
if (ref_cmd->scst_aen)
scst_set_aen_delivery_status(ref_cmd->scst_aen,
SCST_AEN_RES_FAILED);
} else {
if (ref_cmd->scst_cmd)
scst_set_delivery_status(ref_cmd->scst_cmd,
SCST_CMD_DELIVERY_FAILED);
}
}
goto out;
}
static void exit_tx(struct iscsi_conn *conn, int res)
{
iscsi_extracheck_is_wr_thread(conn);
switch (res) {
case -EAGAIN:
case -ERESTARTSYS:
break;
default:
#ifndef CONFIG_SCST_DEBUG
if (!conn->closing) {
#else
{
#endif
PRINT_ERROR("Sending data failed: initiator %s (conn %p), write_size %d, write_state %d, res %d",
conn->session->initiator_name, conn,
conn->write_size,
conn->write_state, res);
}
conn->write_state = TX_END;
conn->write_size = 0;
mark_conn_closed(conn);
break;
}
return;
}
static int tx_ddigest(struct iscsi_cmnd *cmnd, int state)
{
int res, rest = cmnd->conn->write_size;
struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
struct kvec iov;
iscsi_extracheck_is_wr_thread(cmnd->conn);
TRACE_DBG("Sending data digest %x (cmd %p)", cmnd->ddigest, cmnd);
iov.iov_base = (char *)(&cmnd->ddigest) + (sizeof(u32) - rest);
iov.iov_len = rest;
res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
if (res > 0) {
cmnd->conn->write_size -= res;
if (!cmnd->conn->write_size)
cmnd->conn->write_state = state;
} else
exit_tx(cmnd->conn, res);
return res;
}
static void init_tx_hdigest(struct iscsi_cmnd *cmnd)
{
struct iscsi_conn *conn = cmnd->conn;
struct kvec *iop;
iscsi_extracheck_is_wr_thread(conn);
digest_tx_header(cmnd);
sBUG_ON(conn->write_iop_used >= ARRAY_SIZE(conn->write_iov));
iop = &conn->write_iop[conn->write_iop_used];
conn->write_iop_used++;
iop->iov_base = &cmnd->hdigest;
iop->iov_len = sizeof(u32);
conn->write_size += sizeof(u32);
return;
}
static int tx_padding(struct iscsi_cmnd *cmnd, int state)
{
int res, rest = cmnd->conn->write_size;
struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT};
struct kvec iov;
static const uint32_t padding;
BUG_ON(rest < 1);
BUG_ON(rest >= sizeof(uint32_t));
iscsi_extracheck_is_wr_thread(cmnd->conn);
TRACE_DBG("Sending %d padding bytes (cmd %p)", rest, cmnd);
iov.iov_base = (char *)&padding;
iov.iov_len = rest;
res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest);
if (res > 0) {
cmnd->conn->write_size -= res;
if (!cmnd->conn->write_size)
cmnd->conn->write_state = state;
} else
exit_tx(cmnd->conn, res);
return res;
}
static int iscsi_do_send(struct iscsi_conn *conn, int state)
{
int res;
iscsi_extracheck_is_wr_thread(conn);
res = write_data(conn);
if (res > 0) {
if (!conn->write_size)
conn->write_state = state;
} else
exit_tx(conn, res);
return res;
}
/*
* No locks, conn is wr processing.
*
* IMPORTANT! Connection conn must be protected by additional conn_get()
* upon entrance in this function, because otherwise it could be destroyed
* inside as a result of cmnd release.
*/
int iscsi_send(struct iscsi_conn *conn)
{
struct iscsi_cmnd *cmnd = conn->write_cmnd;
int ddigest, res = 0;
TRACE_ENTRY();
TRACE_DBG("conn %p, write_cmnd %p", conn, cmnd);
iscsi_extracheck_is_wr_thread(conn);
ddigest = conn->ddigest_type != DIGEST_NONE ? 1 : 0;
switch (conn->write_state) {
case TX_INIT:
sBUG_ON(cmnd != NULL);
cmnd = conn->write_cmnd = iscsi_get_send_cmnd(conn);
if (!cmnd)
goto out;
cmnd_tx_start(cmnd);
if (!(conn->hdigest_type & DIGEST_NONE))
init_tx_hdigest(cmnd);
conn->write_state = TX_BHS_DATA;
fallthrough;
case TX_BHS_DATA:
res = iscsi_do_send(conn, cmnd->pdu.datasize ?
TX_INIT_PADDING : TX_END);
if (res <= 0 || conn->write_state != TX_INIT_PADDING)
break;
fallthrough;
case TX_INIT_PADDING:
cmnd->conn->write_size = ((cmnd->pdu.datasize + 3) & -4) -
cmnd->pdu.datasize;
if (cmnd->conn->write_size != 0)
conn->write_state = TX_PADDING;
else if (ddigest)
conn->write_state = TX_INIT_DDIGEST;
else
conn->write_state = TX_END;
break;
case TX_PADDING:
res = tx_padding(cmnd, ddigest ? TX_INIT_DDIGEST : TX_END);
if (res <= 0 || conn->write_state != TX_INIT_DDIGEST)
break;
fallthrough;
case TX_INIT_DDIGEST:
cmnd->conn->write_size = sizeof(u32);
conn->write_state = TX_DDIGEST;
fallthrough;
case TX_DDIGEST:
res = tx_ddigest(cmnd, TX_END);
break;
default:
PRINT_CRIT_ERROR("%d %d %x", res, conn->write_state,
cmnd_opcode(cmnd));
sBUG();
}
if (conn->write_state != TX_END)
goto out;
if (unlikely(conn->write_size)) {
PRINT_CRIT_ERROR("%d %x %u", res, cmnd_opcode(cmnd),
conn->write_size);
sBUG();
}
cmnd_tx_end(cmnd);
rsp_cmnd_release(cmnd);
conn->write_cmnd = NULL;
conn->write_state = TX_INIT;
out:
TRACE_EXIT_RES(res);
return res;
}
/*
* Called under wr_lock and BHs disabled, but will drop it inside,
* then reacquire.
*/
static void scst_do_job_wr(struct iscsi_thread_pool *p)
__acquires(&wr_lock)
__releases(&wr_lock)
{
TRACE_ENTRY();
/*
* We delete/add to tail connections to maintain fairness between them.
*/
while (!list_empty(&p->wr_list)) {
int rc;
struct iscsi_conn *conn = list_first_entry(&p->wr_list,
typeof(*conn), wr_list_entry);
TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d, write ready %d",
conn, conn->wr_state,
conn->wr_space_ready, test_write_ready(conn));
list_del(&conn->wr_list_entry);
sBUG_ON(conn->wr_state == ISCSI_CONN_WR_STATE_PROCESSING);
conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING;
conn->wr_space_ready = 0;
#ifdef CONFIG_SCST_EXTRACHECKS
conn->wr_task = current;
#endif
spin_unlock_bh(&p->wr_lock);
conn_get(conn);
rc = iscsi_send(conn);
spin_lock_bh(&p->wr_lock);
#ifdef CONFIG_SCST_EXTRACHECKS
conn->wr_task = NULL;
#endif
if ((rc == -EAGAIN) && !conn->wr_space_ready) {
TRACE_DBG("EAGAIN, setting WR_STATE_SPACE_WAIT (conn %p)",
conn);
conn->wr_state = ISCSI_CONN_WR_STATE_SPACE_WAIT;
} else if (test_write_ready(conn)) {
list_add_tail(&conn->wr_list_entry, &p->wr_list);
conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST;
} else
conn->wr_state = ISCSI_CONN_WR_STATE_IDLE;
conn_put(conn);
}
TRACE_EXIT();
return;
}
static inline int test_wr_list(struct iscsi_thread_pool *p)
{
int res = !list_empty(&p->wr_list) ||
unlikely(kthread_should_stop());
return res;
}
int istwr(void *arg)
{
struct iscsi_thread_pool *p = arg;
int rc;
TRACE_ENTRY();
PRINT_INFO("Write thread for pool %p started", p);
current->flags |= PF_NOFREEZE;
rc = set_cpus_allowed_ptr(current, &p->cpu_mask);
if (rc != 0)
PRINT_ERROR("Setting CPU affinity failed: %d", rc);
spin_lock_bh(&p->wr_lock);
while (!kthread_should_stop()) {
wait_event_locked(p->wr_waitQ, test_wr_list(p), lock_bh,
p->wr_lock);
scst_do_job_wr(p);
}
spin_unlock_bh(&p->wr_lock);
/*
* If kthread_should_stop() is true, we are guaranteed to be
* on the module unload, so wr_list must be empty.
*/
sBUG_ON(!list_empty(&p->wr_list));
PRINT_INFO("Write thread for pool %p finished", p);
TRACE_EXIT();
return 0;
}