5df8085b9e
It should be sufficient to hold the lock just for removing the session from the session list. Everything else should be covered by the session specific lock. On top of that, at present we can get a deadlock caused by waiting on the CAM SIM reference count while holding the global lock. A specific scenario involving ZFS is this: - concurrent termination of two sessions, S1 and S2 - session S1 completed all I/Os and sleeps in CAM waiting for device close by ZFS; - session S2 is also dead now, but can not forcefully complete outstanding requests by calling iscsi_session_cleanup() from iscsi_maintenance_thread_terminate(), since it can't get the same global sc_lock; - as soon as there are unfinished requests, ZFS can not do spa_config_enter() as writer, and so can not close the device for session S1; - deadlock. Reported by: Ben RUBSON <ben.rubson@gmail.com> Tested by: Ben RUBSON <ben.rubson@gmail.com> Reviewed by: mav, trasz MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D12652
2571 lines
67 KiB
C
2571 lines
67 KiB
C
/*-
|
|
* Copyright (c) 2012 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Edward Tomasz Napierala under sponsorship
|
|
* from the FreeBSD Foundation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/condvar.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/eventhandler.h>
|
|
#include <sys/file.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/kthread.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/module.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sx.h>
|
|
#include <vm/uma.h>
|
|
|
|
#include <cam/cam.h>
|
|
#include <cam/cam_ccb.h>
|
|
#include <cam/cam_xpt.h>
|
|
#include <cam/cam_debug.h>
|
|
#include <cam/cam_sim.h>
|
|
#include <cam/cam_xpt_sim.h>
|
|
#include <cam/cam_xpt_periph.h>
|
|
#include <cam/cam_periph.h>
|
|
#include <cam/scsi/scsi_all.h>
|
|
#include <cam/scsi/scsi_message.h>
|
|
|
|
#include <dev/iscsi/icl.h>
|
|
#include <dev/iscsi/icl_wrappers.h>
|
|
#include <dev/iscsi/iscsi_ioctl.h>
|
|
#include <dev/iscsi/iscsi_proto.h>
|
|
#include <dev/iscsi/iscsi.h>
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
#include <sys/socketvar.h>
|
|
#endif
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
FEATURE(iscsi_kernel_proxy, "iSCSI initiator built with ICL_KERNEL_PROXY");
|
|
#endif
|
|
|
|
/*
|
|
* XXX: This is global so the iscsi_unload() can access it.
|
|
* Think about how to do this properly.
|
|
*/
|
|
static struct iscsi_softc *sc;
|
|
|
|
SYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD, 0, "iSCSI initiator");
|
|
static int debug = 1;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RWTUN,
|
|
&debug, 0, "Enable debug messages");
|
|
static int ping_timeout = 5;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RWTUN, &ping_timeout,
|
|
0, "Timeout for ping (NOP-Out) requests, in seconds");
|
|
static int iscsid_timeout = 60;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, iscsid_timeout, CTLFLAG_RWTUN, &iscsid_timeout,
|
|
0, "Time to wait for iscsid(8) to handle reconnection, in seconds");
|
|
static int login_timeout = 60;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, login_timeout, CTLFLAG_RWTUN, &login_timeout,
|
|
0, "Time to wait for iscsid(8) to finish Login Phase, in seconds");
|
|
static int maxtags = 255;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, maxtags, CTLFLAG_RWTUN, &maxtags,
|
|
0, "Max number of IO requests queued");
|
|
static int fail_on_disconnection = 0;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_disconnection, CTLFLAG_RWTUN,
|
|
&fail_on_disconnection, 0, "Destroy CAM SIM on connection failure");
|
|
static int fail_on_shutdown = 1;
|
|
SYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_shutdown, CTLFLAG_RWTUN,
|
|
&fail_on_shutdown, 0, "Fail disconnected sessions on shutdown");
|
|
|
|
static MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI initiator");
|
|
static uma_zone_t iscsi_outstanding_zone;
|
|
|
|
#define CONN_SESSION(X) ((struct iscsi_session *)X->ic_prv0)
|
|
#define PDU_SESSION(X) (CONN_SESSION(X->ip_conn))
|
|
|
|
#define ISCSI_DEBUG(X, ...) \
|
|
do { \
|
|
if (debug > 1) \
|
|
printf("%s: " X "\n", __func__, ## __VA_ARGS__);\
|
|
} while (0)
|
|
|
|
#define ISCSI_WARN(X, ...) \
|
|
do { \
|
|
if (debug > 0) { \
|
|
printf("WARNING: %s: " X "\n", \
|
|
__func__, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ISCSI_SESSION_DEBUG(S, X, ...) \
|
|
do { \
|
|
if (debug > 1) { \
|
|
printf("%s: %s (%s): " X "\n", \
|
|
__func__, S->is_conf.isc_target_addr, \
|
|
S->is_conf.isc_target, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ISCSI_SESSION_WARN(S, X, ...) \
|
|
do { \
|
|
if (debug > 0) { \
|
|
printf("WARNING: %s (%s): " X "\n", \
|
|
S->is_conf.isc_target_addr, \
|
|
S->is_conf.isc_target, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define ISCSI_SESSION_LOCK(X) mtx_lock(&X->is_lock)
|
|
#define ISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->is_lock)
|
|
#define ISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->is_lock, MA_OWNED)
|
|
#define ISCSI_SESSION_LOCK_ASSERT_NOT(X) mtx_assert(&X->is_lock, MA_NOTOWNED)
|
|
|
|
static int iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg,
|
|
int mode, struct thread *td);
|
|
|
|
static struct cdevsw iscsi_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_ioctl = iscsi_ioctl,
|
|
.d_name = "iscsi",
|
|
};
|
|
|
|
static void iscsi_pdu_queue_locked(struct icl_pdu *request);
|
|
static void iscsi_pdu_queue(struct icl_pdu *request);
|
|
static void iscsi_pdu_update_statsn(const struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_nop_in(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_scsi_response(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_task_response(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_data_in(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_logout_response(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_r2t(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_async_message(struct icl_pdu *response);
|
|
static void iscsi_pdu_handle_reject(struct icl_pdu *response);
|
|
static void iscsi_session_reconnect(struct iscsi_session *is);
|
|
static void iscsi_session_terminate(struct iscsi_session *is);
|
|
static void iscsi_action(struct cam_sim *sim, union ccb *ccb);
|
|
static void iscsi_poll(struct cam_sim *sim);
|
|
static struct iscsi_outstanding *iscsi_outstanding_find(struct iscsi_session *is,
|
|
uint32_t initiator_task_tag);
|
|
static struct iscsi_outstanding *iscsi_outstanding_add(struct iscsi_session *is,
|
|
struct icl_pdu *request, union ccb *ccb,
|
|
uint32_t *initiator_task_tagp);
|
|
static void iscsi_outstanding_remove(struct iscsi_session *is,
|
|
struct iscsi_outstanding *io);
|
|
|
|
static bool
|
|
iscsi_pdu_prepare(struct icl_pdu *request)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct iscsi_bhs_scsi_command *bhssc;
|
|
|
|
is = PDU_SESSION(request);
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
/*
|
|
* We're only using fields common for all the request
|
|
* (initiator -> target) PDUs.
|
|
*/
|
|
bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs;
|
|
|
|
/*
|
|
* Data-Out PDU does not contain CmdSN.
|
|
*/
|
|
if (bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_OUT) {
|
|
if (ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn) &&
|
|
(bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) {
|
|
/*
|
|
* Current MaxCmdSN prevents us from sending any more
|
|
* SCSI Command PDUs to the target; postpone the PDU.
|
|
* It will get resent by either iscsi_pdu_queue(),
|
|
* or by maintenance thread.
|
|
*/
|
|
#if 0
|
|
ISCSI_SESSION_DEBUG(is, "postponing send, CmdSN %u, "
|
|
"ExpCmdSN %u, MaxCmdSN %u, opcode 0x%x",
|
|
is->is_cmdsn, is->is_expcmdsn, is->is_maxcmdsn,
|
|
bhssc->bhssc_opcode);
|
|
#endif
|
|
return (true);
|
|
}
|
|
bhssc->bhssc_cmdsn = htonl(is->is_cmdsn);
|
|
if ((bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
|
|
is->is_cmdsn++;
|
|
}
|
|
bhssc->bhssc_expstatsn = htonl(is->is_statsn + 1);
|
|
|
|
return (false);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_send_postponed(struct iscsi_session *is)
|
|
{
|
|
struct icl_pdu *request;
|
|
bool postpone;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
if (STAILQ_EMPTY(&is->is_postponed))
|
|
return;
|
|
while ((request = STAILQ_FIRST(&is->is_postponed)) != NULL) {
|
|
postpone = iscsi_pdu_prepare(request);
|
|
if (postpone)
|
|
return;
|
|
STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next);
|
|
icl_pdu_queue(request);
|
|
}
|
|
xpt_release_simq(is->is_sim, 1);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_queue_locked(struct icl_pdu *request)
|
|
{
|
|
struct iscsi_session *is;
|
|
bool postpone;
|
|
|
|
is = PDU_SESSION(request);
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
iscsi_session_send_postponed(is);
|
|
postpone = iscsi_pdu_prepare(request);
|
|
if (postpone) {
|
|
if (STAILQ_EMPTY(&is->is_postponed))
|
|
xpt_freeze_simq(is->is_sim, 1);
|
|
STAILQ_INSERT_TAIL(&is->is_postponed, request, ip_next);
|
|
return;
|
|
}
|
|
icl_pdu_queue(request);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_queue(struct icl_pdu *request)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
is = PDU_SESSION(request);
|
|
ISCSI_SESSION_LOCK(is);
|
|
iscsi_pdu_queue_locked(request);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_logout(struct iscsi_session *is)
|
|
{
|
|
struct icl_pdu *request;
|
|
struct iscsi_bhs_logout_request *bhslr;
|
|
|
|
request = icl_pdu_new(is->is_conn, M_NOWAIT);
|
|
if (request == NULL)
|
|
return;
|
|
|
|
bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs;
|
|
bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST;
|
|
bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION;
|
|
iscsi_pdu_queue_locked(request);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_terminate_task(struct iscsi_session *is,
|
|
struct iscsi_outstanding *io, bool requeue)
|
|
{
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
if (io->io_ccb != NULL) {
|
|
io->io_ccb->ccb_h.status &= ~(CAM_SIM_QUEUED | CAM_STATUS_MASK);
|
|
if (requeue)
|
|
io->io_ccb->ccb_h.status |= CAM_REQUEUE_REQ;
|
|
else
|
|
io->io_ccb->ccb_h.status |= CAM_REQ_ABORTED;
|
|
if ((io->io_ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
io->io_ccb->ccb_h.status |= CAM_DEV_QFRZN;
|
|
xpt_freeze_devq(io->io_ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
xpt_done(io->io_ccb);
|
|
}
|
|
iscsi_outstanding_remove(is, io);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_terminate_tasks(struct iscsi_session *is, bool requeue)
|
|
{
|
|
struct iscsi_outstanding *io, *tmp;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
TAILQ_FOREACH_SAFE(io, &is->is_outstanding, io_next, tmp) {
|
|
iscsi_session_terminate_task(is, io, requeue);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iscsi_session_cleanup(struct iscsi_session *is, bool destroy_sim)
|
|
{
|
|
struct icl_pdu *pdu;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
/*
|
|
* Don't queue any new PDUs.
|
|
*/
|
|
if (is->is_sim != NULL && is->is_simq_frozen == false) {
|
|
ISCSI_SESSION_DEBUG(is, "freezing");
|
|
xpt_freeze_simq(is->is_sim, 1);
|
|
is->is_simq_frozen = true;
|
|
}
|
|
|
|
/*
|
|
* Remove postponed PDUs.
|
|
*/
|
|
if (!STAILQ_EMPTY(&is->is_postponed))
|
|
xpt_release_simq(is->is_sim, 1);
|
|
while ((pdu = STAILQ_FIRST(&is->is_postponed)) != NULL) {
|
|
STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next);
|
|
icl_pdu_free(pdu);
|
|
}
|
|
|
|
if (destroy_sim == false) {
|
|
/*
|
|
* Terminate SCSI tasks, asking CAM to requeue them.
|
|
*/
|
|
iscsi_session_terminate_tasks(is, true);
|
|
return;
|
|
}
|
|
|
|
iscsi_session_terminate_tasks(is, false);
|
|
|
|
if (is->is_sim == NULL)
|
|
return;
|
|
|
|
ISCSI_SESSION_DEBUG(is, "deregistering SIM");
|
|
xpt_async(AC_LOST_DEVICE, is->is_path, NULL);
|
|
|
|
if (is->is_simq_frozen) {
|
|
xpt_release_simq(is->is_sim, 1);
|
|
is->is_simq_frozen = false;
|
|
}
|
|
|
|
xpt_free_path(is->is_path);
|
|
is->is_path = NULL;
|
|
xpt_bus_deregister(cam_sim_path(is->is_sim));
|
|
cam_sim_free(is->is_sim, TRUE /*free_devq*/);
|
|
is->is_sim = NULL;
|
|
is->is_devq = NULL;
|
|
}
|
|
|
|
static void
|
|
iscsi_maintenance_thread_reconnect(struct iscsi_session *is)
|
|
{
|
|
|
|
icl_conn_close(is->is_conn);
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
|
|
is->is_connected = false;
|
|
is->is_reconnecting = false;
|
|
is->is_login_phase = false;
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
if (is->is_login_pdu != NULL) {
|
|
icl_pdu_free(is->is_login_pdu);
|
|
is->is_login_pdu = NULL;
|
|
}
|
|
cv_signal(&is->is_login_cv);
|
|
#endif
|
|
|
|
if (fail_on_disconnection) {
|
|
ISCSI_SESSION_DEBUG(is, "connection failed, destroying devices");
|
|
iscsi_session_cleanup(is, true);
|
|
} else {
|
|
iscsi_session_cleanup(is, false);
|
|
}
|
|
|
|
KASSERT(TAILQ_EMPTY(&is->is_outstanding),
|
|
("destroying session with active tasks"));
|
|
KASSERT(STAILQ_EMPTY(&is->is_postponed),
|
|
("destroying session with postponed PDUs"));
|
|
|
|
if (is->is_conf.isc_enable == 0 && is->is_conf.isc_discovery == 0) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Request immediate reconnection from iscsid(8).
|
|
*/
|
|
//ISCSI_SESSION_DEBUG(is, "waking up iscsid(8)");
|
|
is->is_waiting_for_iscsid = true;
|
|
strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason));
|
|
is->is_timeout = 0;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
cv_signal(&is->is_softc->sc_cv);
|
|
}
|
|
|
|
static void
|
|
iscsi_maintenance_thread_terminate(struct iscsi_session *is)
|
|
{
|
|
struct iscsi_softc *sc;
|
|
|
|
sc = is->is_softc;
|
|
sx_xlock(&sc->sc_lock);
|
|
TAILQ_REMOVE(&sc->sc_sessions, is, is_next);
|
|
sx_xunlock(&sc->sc_lock);
|
|
|
|
icl_conn_close(is->is_conn);
|
|
callout_drain(&is->is_callout);
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
|
|
KASSERT(is->is_terminating, ("is_terminating == false"));
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
if (is->is_login_pdu != NULL) {
|
|
icl_pdu_free(is->is_login_pdu);
|
|
is->is_login_pdu = NULL;
|
|
}
|
|
cv_signal(&is->is_login_cv);
|
|
#endif
|
|
|
|
iscsi_session_cleanup(is, true);
|
|
|
|
KASSERT(TAILQ_EMPTY(&is->is_outstanding),
|
|
("destroying session with active tasks"));
|
|
KASSERT(STAILQ_EMPTY(&is->is_postponed),
|
|
("destroying session with postponed PDUs"));
|
|
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
icl_conn_free(is->is_conn);
|
|
mtx_destroy(&is->is_lock);
|
|
cv_destroy(&is->is_maintenance_cv);
|
|
#ifdef ICL_KERNEL_PROXY
|
|
cv_destroy(&is->is_login_cv);
|
|
#endif
|
|
|
|
ISCSI_SESSION_DEBUG(is, "terminated");
|
|
free(is, M_ISCSI);
|
|
|
|
/*
|
|
* The iscsi_unload() routine might be waiting.
|
|
*/
|
|
cv_signal(&sc->sc_cv);
|
|
}
|
|
|
|
static void
|
|
iscsi_maintenance_thread(void *arg)
|
|
{
|
|
struct iscsi_session *is = arg;
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
for (;;) {
|
|
if (is->is_reconnecting == false &&
|
|
is->is_terminating == false &&
|
|
(STAILQ_EMPTY(&is->is_postponed) ||
|
|
ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn)))
|
|
cv_wait(&is->is_maintenance_cv, &is->is_lock);
|
|
|
|
/* Terminate supersedes reconnect. */
|
|
if (is->is_terminating) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
iscsi_maintenance_thread_terminate(is);
|
|
kthread_exit();
|
|
return;
|
|
}
|
|
|
|
if (is->is_reconnecting) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
iscsi_maintenance_thread_reconnect(is);
|
|
ISCSI_SESSION_LOCK(is);
|
|
continue;
|
|
}
|
|
|
|
iscsi_session_send_postponed(is);
|
|
}
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_reconnect(struct iscsi_session *is)
|
|
{
|
|
|
|
/*
|
|
* XXX: We can't use locking here, because
|
|
* it's being called from various contexts.
|
|
* Hope it doesn't break anything.
|
|
*/
|
|
if (is->is_reconnecting)
|
|
return;
|
|
|
|
is->is_reconnecting = true;
|
|
cv_signal(&is->is_maintenance_cv);
|
|
}
|
|
|
|
static void
|
|
iscsi_session_terminate(struct iscsi_session *is)
|
|
{
|
|
|
|
if (is->is_terminating)
|
|
return;
|
|
|
|
is->is_terminating = true;
|
|
|
|
#if 0
|
|
iscsi_session_logout(is);
|
|
#endif
|
|
cv_signal(&is->is_maintenance_cv);
|
|
}
|
|
|
|
static void
|
|
iscsi_callout(void *context)
|
|
{
|
|
struct icl_pdu *request;
|
|
struct iscsi_bhs_nop_out *bhsno;
|
|
struct iscsi_session *is;
|
|
bool reconnect_needed = false;
|
|
|
|
is = context;
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
if (is->is_terminating) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
callout_schedule(&is->is_callout, 1 * hz);
|
|
|
|
if (is->is_conf.isc_enable == 0)
|
|
goto out;
|
|
|
|
is->is_timeout++;
|
|
|
|
if (is->is_waiting_for_iscsid) {
|
|
if (iscsid_timeout > 0 && is->is_timeout > iscsid_timeout) {
|
|
ISCSI_SESSION_WARN(is, "timed out waiting for iscsid(8) "
|
|
"for %d seconds; reconnecting",
|
|
is->is_timeout);
|
|
reconnect_needed = true;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (is->is_login_phase) {
|
|
if (login_timeout > 0 && is->is_timeout > login_timeout) {
|
|
ISCSI_SESSION_WARN(is, "login timed out after %d seconds; "
|
|
"reconnecting", is->is_timeout);
|
|
reconnect_needed = true;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (ping_timeout <= 0) {
|
|
/*
|
|
* Pings are disabled. Don't send NOP-Out in this case.
|
|
* Reset the timeout, to avoid triggering reconnection,
|
|
* should the user decide to reenable them.
|
|
*/
|
|
is->is_timeout = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (is->is_timeout >= ping_timeout) {
|
|
ISCSI_SESSION_WARN(is, "no ping reply (NOP-In) after %d seconds; "
|
|
"reconnecting", ping_timeout);
|
|
reconnect_needed = true;
|
|
goto out;
|
|
}
|
|
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
/*
|
|
* If the ping was reset less than one second ago - which means
|
|
* that we've received some PDU during the last second - assume
|
|
* the traffic flows correctly and don't bother sending a NOP-Out.
|
|
*
|
|
* (It's 2 - one for one second, and one for incrementing is_timeout
|
|
* earlier in this routine.)
|
|
*/
|
|
if (is->is_timeout < 2)
|
|
return;
|
|
|
|
request = icl_pdu_new(is->is_conn, M_NOWAIT);
|
|
if (request == NULL) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate PDU");
|
|
return;
|
|
}
|
|
bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs;
|
|
bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT |
|
|
ISCSI_BHS_OPCODE_IMMEDIATE;
|
|
bhsno->bhsno_flags = 0x80;
|
|
bhsno->bhsno_target_transfer_tag = 0xffffffff;
|
|
iscsi_pdu_queue(request);
|
|
return;
|
|
|
|
out:
|
|
if (is->is_terminating) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
if (reconnect_needed)
|
|
iscsi_session_reconnect(is);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_update_statsn(const struct icl_pdu *response)
|
|
{
|
|
const struct iscsi_bhs_data_in *bhsdi;
|
|
struct iscsi_session *is;
|
|
uint32_t expcmdsn, maxcmdsn, statsn;
|
|
|
|
is = PDU_SESSION(response);
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
/*
|
|
* We're only using fields common for all the response
|
|
* (target -> initiator) PDUs.
|
|
*/
|
|
bhsdi = (const struct iscsi_bhs_data_in *)response->ip_bhs;
|
|
/*
|
|
* Ok, I lied. In case of Data-In, "The fields StatSN, Status,
|
|
* and Residual Count only have meaningful content if the S bit
|
|
* is set to 1", so we also need to check the bit specific for
|
|
* Data-In PDU.
|
|
*/
|
|
if (bhsdi->bhsdi_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN ||
|
|
(bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0) {
|
|
statsn = ntohl(bhsdi->bhsdi_statsn);
|
|
if (statsn != is->is_statsn && statsn != (is->is_statsn + 1)) {
|
|
/* XXX: This is normal situation for MCS */
|
|
ISCSI_SESSION_WARN(is, "PDU 0x%x StatSN %u != "
|
|
"session ExpStatSN %u (or + 1); reconnecting",
|
|
bhsdi->bhsdi_opcode, statsn, is->is_statsn);
|
|
iscsi_session_reconnect(is);
|
|
}
|
|
if (ISCSI_SNGT(statsn, is->is_statsn))
|
|
is->is_statsn = statsn;
|
|
}
|
|
|
|
expcmdsn = ntohl(bhsdi->bhsdi_expcmdsn);
|
|
maxcmdsn = ntohl(bhsdi->bhsdi_maxcmdsn);
|
|
|
|
if (ISCSI_SNLT(maxcmdsn + 1, expcmdsn)) {
|
|
ISCSI_SESSION_DEBUG(is,
|
|
"PDU MaxCmdSN %u + 1 < PDU ExpCmdSN %u; ignoring",
|
|
maxcmdsn, expcmdsn);
|
|
} else {
|
|
if (ISCSI_SNGT(maxcmdsn, is->is_maxcmdsn)) {
|
|
is->is_maxcmdsn = maxcmdsn;
|
|
|
|
/*
|
|
* Command window increased; kick the maintanance thread
|
|
* to send out postponed commands.
|
|
*/
|
|
if (!STAILQ_EMPTY(&is->is_postponed))
|
|
cv_signal(&is->is_maintenance_cv);
|
|
} else if (ISCSI_SNLT(maxcmdsn, is->is_maxcmdsn)) {
|
|
/* XXX: This is normal situation for MCS */
|
|
ISCSI_SESSION_DEBUG(is,
|
|
"PDU MaxCmdSN %u < session MaxCmdSN %u; ignoring",
|
|
maxcmdsn, is->is_maxcmdsn);
|
|
}
|
|
|
|
if (ISCSI_SNGT(expcmdsn, is->is_expcmdsn)) {
|
|
is->is_expcmdsn = expcmdsn;
|
|
} else if (ISCSI_SNLT(expcmdsn, is->is_expcmdsn)) {
|
|
/* XXX: This is normal situation for MCS */
|
|
ISCSI_SESSION_DEBUG(is,
|
|
"PDU ExpCmdSN %u < session ExpCmdSN %u; ignoring",
|
|
expcmdsn, is->is_expcmdsn);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Every incoming PDU - not just NOP-In - resets the ping timer.
|
|
* The purpose of the timeout is to reset the connection when it stalls;
|
|
* we don't want this to happen when NOP-In or NOP-Out ends up delayed
|
|
* in some queue.
|
|
*/
|
|
is->is_timeout = 0;
|
|
}
|
|
|
|
static void
|
|
iscsi_receive_callback(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
is = PDU_SESSION(response);
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
|
|
iscsi_pdu_update_statsn(response);
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
if (is->is_login_phase) {
|
|
if (is->is_login_pdu == NULL)
|
|
is->is_login_pdu = response;
|
|
else
|
|
icl_pdu_free(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
cv_signal(&is->is_login_cv);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The handling routine is responsible for freeing the PDU
|
|
* when it's no longer needed.
|
|
*/
|
|
switch (response->ip_bhs->bhs_opcode) {
|
|
case ISCSI_BHS_OPCODE_NOP_IN:
|
|
iscsi_pdu_handle_nop_in(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_SCSI_RESPONSE:
|
|
iscsi_pdu_handle_scsi_response(response);
|
|
/* Session lock dropped inside. */
|
|
ISCSI_SESSION_LOCK_ASSERT_NOT(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_TASK_RESPONSE:
|
|
iscsi_pdu_handle_task_response(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_SCSI_DATA_IN:
|
|
iscsi_pdu_handle_data_in(response);
|
|
/* Session lock dropped inside. */
|
|
ISCSI_SESSION_LOCK_ASSERT_NOT(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_LOGOUT_RESPONSE:
|
|
iscsi_pdu_handle_logout_response(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_R2T:
|
|
iscsi_pdu_handle_r2t(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_ASYNC_MESSAGE:
|
|
iscsi_pdu_handle_async_message(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
case ISCSI_BHS_OPCODE_REJECT:
|
|
iscsi_pdu_handle_reject(response);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
break;
|
|
default:
|
|
ISCSI_SESSION_WARN(is, "received PDU with unsupported "
|
|
"opcode 0x%x; reconnecting",
|
|
response->ip_bhs->bhs_opcode);
|
|
iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
icl_pdu_free(response);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iscsi_error_callback(struct icl_conn *ic)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
is = CONN_SESSION(ic);
|
|
|
|
ISCSI_SESSION_WARN(is, "connection error; reconnecting");
|
|
iscsi_session_reconnect(is);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_nop_in(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct iscsi_bhs_nop_out *bhsno;
|
|
struct iscsi_bhs_nop_in *bhsni;
|
|
struct icl_pdu *request;
|
|
void *data = NULL;
|
|
size_t datasize;
|
|
int error;
|
|
|
|
is = PDU_SESSION(response);
|
|
bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs;
|
|
|
|
if (bhsni->bhsni_target_transfer_tag == 0xffffffff) {
|
|
/*
|
|
* Nothing to do; iscsi_pdu_update_statsn() already
|
|
* zeroed the timeout.
|
|
*/
|
|
icl_pdu_free(response);
|
|
return;
|
|
}
|
|
|
|
datasize = icl_pdu_data_segment_length(response);
|
|
if (datasize > 0) {
|
|
data = malloc(datasize, M_ISCSI, M_NOWAIT | M_ZERO);
|
|
if (data == NULL) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate memory; "
|
|
"reconnecting");
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
icl_pdu_get_data(response, 0, data, datasize);
|
|
}
|
|
|
|
request = icl_pdu_new(response->ip_conn, M_NOWAIT);
|
|
if (request == NULL) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate memory; "
|
|
"reconnecting");
|
|
free(data, M_ISCSI);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs;
|
|
bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT |
|
|
ISCSI_BHS_OPCODE_IMMEDIATE;
|
|
bhsno->bhsno_flags = 0x80;
|
|
bhsno->bhsno_initiator_task_tag = 0xffffffff;
|
|
bhsno->bhsno_target_transfer_tag = bhsni->bhsni_target_transfer_tag;
|
|
if (datasize > 0) {
|
|
error = icl_pdu_append_data(request, data, datasize, M_NOWAIT);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate memory; "
|
|
"reconnecting");
|
|
free(data, M_ISCSI);
|
|
icl_pdu_free(request);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
free(data, M_ISCSI);
|
|
}
|
|
|
|
icl_pdu_free(response);
|
|
iscsi_pdu_queue_locked(request);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_scsi_response(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_bhs_scsi_response *bhssr;
|
|
struct iscsi_outstanding *io;
|
|
struct iscsi_session *is;
|
|
union ccb *ccb;
|
|
struct ccb_scsiio *csio;
|
|
size_t data_segment_len, received;
|
|
uint16_t sense_len;
|
|
uint32_t resid;
|
|
|
|
is = PDU_SESSION(response);
|
|
|
|
bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs;
|
|
io = iscsi_outstanding_find(is, bhssr->bhssr_initiator_task_tag);
|
|
if (io == NULL || io->io_ccb == NULL) {
|
|
ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhssr->bhssr_initiator_task_tag);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
ccb = io->io_ccb;
|
|
|
|
/*
|
|
* With iSER, after getting good response we can be sure
|
|
* that all the data has been successfully transferred.
|
|
*/
|
|
if (is->is_conn->ic_iser) {
|
|
resid = ntohl(bhssr->bhssr_residual_count);
|
|
if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_UNDERFLOW) {
|
|
io->io_received = ccb->csio.dxfer_len - resid;
|
|
} else if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_OVERFLOW) {
|
|
ISCSI_SESSION_WARN(is, "overflow: target indicates %d", resid);
|
|
} else {
|
|
io->io_received = ccb->csio.dxfer_len;
|
|
}
|
|
}
|
|
|
|
received = io->io_received;
|
|
iscsi_outstanding_remove(is, io);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
if (bhssr->bhssr_response != BHSSR_RESPONSE_COMMAND_COMPLETED) {
|
|
ISCSI_SESSION_WARN(is, "service response 0x%x", bhssr->bhssr_response);
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
|
|
} else if (bhssr->bhssr_status == 0) {
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
} else {
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN;
|
|
ccb->csio.scsi_status = bhssr->bhssr_status;
|
|
}
|
|
|
|
csio = &ccb->csio;
|
|
data_segment_len = icl_pdu_data_segment_length(response);
|
|
if (data_segment_len > 0) {
|
|
if (data_segment_len < sizeof(sense_len)) {
|
|
ISCSI_SESSION_WARN(is, "truncated data segment (%zd bytes)",
|
|
data_segment_len);
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
|
|
goto out;
|
|
}
|
|
icl_pdu_get_data(response, 0, &sense_len, sizeof(sense_len));
|
|
sense_len = ntohs(sense_len);
|
|
#if 0
|
|
ISCSI_SESSION_DEBUG(is, "sense_len %d, data len %zd",
|
|
sense_len, data_segment_len);
|
|
#endif
|
|
if (sizeof(sense_len) + sense_len > data_segment_len) {
|
|
ISCSI_SESSION_WARN(is, "truncated data segment "
|
|
"(%zd bytes, should be %zd)",
|
|
data_segment_len, sizeof(sense_len) + sense_len);
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
|
|
goto out;
|
|
} else if (sizeof(sense_len) + sense_len < data_segment_len)
|
|
ISCSI_SESSION_WARN(is, "oversize data segment "
|
|
"(%zd bytes, should be %zd)",
|
|
data_segment_len, sizeof(sense_len) + sense_len);
|
|
if (sense_len > csio->sense_len) {
|
|
ISCSI_SESSION_DEBUG(is, "truncating sense from %d to %d",
|
|
sense_len, csio->sense_len);
|
|
sense_len = csio->sense_len;
|
|
}
|
|
icl_pdu_get_data(response, sizeof(sense_len), &csio->sense_data, sense_len);
|
|
csio->sense_resid = csio->sense_len - sense_len;
|
|
ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
|
|
}
|
|
|
|
out:
|
|
if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_UNDERFLOW)
|
|
csio->resid = ntohl(bhssr->bhssr_residual_count);
|
|
|
|
if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
|
|
KASSERT(received <= csio->dxfer_len,
|
|
("received > csio->dxfer_len"));
|
|
if (received < csio->dxfer_len) {
|
|
if (csio->resid != csio->dxfer_len - received) {
|
|
ISCSI_SESSION_WARN(is, "underflow mismatch: "
|
|
"target indicates %d, we calculated %zd",
|
|
csio->resid, csio->dxfer_len - received);
|
|
}
|
|
csio->resid = csio->dxfer_len - received;
|
|
}
|
|
}
|
|
|
|
xpt_done(ccb);
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_task_response(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_bhs_task_management_response *bhstmr;
|
|
struct iscsi_outstanding *io, *aio;
|
|
struct iscsi_session *is;
|
|
|
|
is = PDU_SESSION(response);
|
|
|
|
bhstmr = (struct iscsi_bhs_task_management_response *)response->ip_bhs;
|
|
io = iscsi_outstanding_find(is, bhstmr->bhstmr_initiator_task_tag);
|
|
if (io == NULL || io->io_ccb != NULL) {
|
|
ISCSI_SESSION_WARN(is, "bad itt 0x%x",
|
|
bhstmr->bhstmr_initiator_task_tag);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
if (bhstmr->bhstmr_response != BHSTMR_RESPONSE_FUNCTION_COMPLETE) {
|
|
ISCSI_SESSION_WARN(is, "task response 0x%x",
|
|
bhstmr->bhstmr_response);
|
|
} else {
|
|
aio = iscsi_outstanding_find(is, io->io_datasn);
|
|
if (aio != NULL && aio->io_ccb != NULL)
|
|
iscsi_session_terminate_task(is, aio, false);
|
|
}
|
|
|
|
iscsi_outstanding_remove(is, io);
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_data_in(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_bhs_data_in *bhsdi;
|
|
struct iscsi_outstanding *io;
|
|
struct iscsi_session *is;
|
|
union ccb *ccb;
|
|
struct ccb_scsiio *csio;
|
|
size_t data_segment_len, received, oreceived;
|
|
|
|
is = PDU_SESSION(response);
|
|
bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs;
|
|
io = iscsi_outstanding_find(is, bhsdi->bhsdi_initiator_task_tag);
|
|
if (io == NULL || io->io_ccb == NULL) {
|
|
ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhsdi->bhsdi_initiator_task_tag);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
data_segment_len = icl_pdu_data_segment_length(response);
|
|
if (data_segment_len == 0) {
|
|
/*
|
|
* "The sending of 0 length data segments should be avoided,
|
|
* but initiators and targets MUST be able to properly receive
|
|
* 0 length data segments."
|
|
*/
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
icl_pdu_free(response);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We need to track this for security reasons - without it, malicious target
|
|
* could respond to SCSI READ without sending Data-In PDUs, which would result
|
|
* in read operation on the initiator side returning random kernel data.
|
|
*/
|
|
if (ntohl(bhsdi->bhsdi_buffer_offset) != io->io_received) {
|
|
ISCSI_SESSION_WARN(is, "data out of order; expected offset %zd, got %zd",
|
|
io->io_received, (size_t)ntohl(bhsdi->bhsdi_buffer_offset));
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
ccb = io->io_ccb;
|
|
csio = &ccb->csio;
|
|
|
|
if (io->io_received + data_segment_len > csio->dxfer_len) {
|
|
ISCSI_SESSION_WARN(is, "oversize data segment (%zd bytes "
|
|
"at offset %zd, buffer is %d)",
|
|
data_segment_len, io->io_received, csio->dxfer_len);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return;
|
|
}
|
|
|
|
oreceived = io->io_received;
|
|
io->io_received += data_segment_len;
|
|
received = io->io_received;
|
|
if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0)
|
|
iscsi_outstanding_remove(is, io);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
icl_pdu_get_data(response, 0, csio->data_ptr + oreceived, data_segment_len);
|
|
|
|
/*
|
|
* XXX: Check DataSN.
|
|
* XXX: Check F.
|
|
*/
|
|
if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) == 0) {
|
|
/*
|
|
* Nothing more to do.
|
|
*/
|
|
icl_pdu_free(response);
|
|
return;
|
|
}
|
|
|
|
//ISCSI_SESSION_DEBUG(is, "got S flag; status 0x%x", bhsdi->bhsdi_status);
|
|
if (bhsdi->bhsdi_status == 0) {
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
} else {
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN;
|
|
csio->scsi_status = bhsdi->bhsdi_status;
|
|
}
|
|
|
|
if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
|
|
KASSERT(received <= csio->dxfer_len,
|
|
("received > csio->dxfer_len"));
|
|
if (received < csio->dxfer_len) {
|
|
csio->resid = ntohl(bhsdi->bhsdi_residual_count);
|
|
if (csio->resid != csio->dxfer_len - received) {
|
|
ISCSI_SESSION_WARN(is, "underflow mismatch: "
|
|
"target indicates %d, we calculated %zd",
|
|
csio->resid, csio->dxfer_len - received);
|
|
}
|
|
csio->resid = csio->dxfer_len - received;
|
|
}
|
|
}
|
|
|
|
xpt_done(ccb);
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_logout_response(struct icl_pdu *response)
|
|
{
|
|
|
|
ISCSI_SESSION_DEBUG(PDU_SESSION(response), "logout response");
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_r2t(struct icl_pdu *response)
|
|
{
|
|
struct icl_pdu *request;
|
|
struct iscsi_session *is;
|
|
struct iscsi_bhs_r2t *bhsr2t;
|
|
struct iscsi_bhs_data_out *bhsdo;
|
|
struct iscsi_outstanding *io;
|
|
struct ccb_scsiio *csio;
|
|
size_t off, len, total_len;
|
|
int error;
|
|
|
|
is = PDU_SESSION(response);
|
|
|
|
bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs;
|
|
io = iscsi_outstanding_find(is, bhsr2t->bhsr2t_initiator_task_tag);
|
|
if (io == NULL || io->io_ccb == NULL) {
|
|
ISCSI_SESSION_WARN(is, "bad itt 0x%x; reconnecting",
|
|
bhsr2t->bhsr2t_initiator_task_tag);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
csio = &io->io_ccb->csio;
|
|
|
|
if ((csio->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_OUT) {
|
|
ISCSI_SESSION_WARN(is, "received R2T for read command; reconnecting");
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* XXX: Verify R2TSN.
|
|
*/
|
|
|
|
io->io_datasn = 0;
|
|
|
|
off = ntohl(bhsr2t->bhsr2t_buffer_offset);
|
|
if (off > csio->dxfer_len) {
|
|
ISCSI_SESSION_WARN(is, "target requested invalid offset "
|
|
"%zd, buffer is is %d; reconnecting", off, csio->dxfer_len);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
total_len = ntohl(bhsr2t->bhsr2t_desired_data_transfer_length);
|
|
if (total_len == 0 || total_len > csio->dxfer_len) {
|
|
ISCSI_SESSION_WARN(is, "target requested invalid length "
|
|
"%zd, buffer is %d; reconnecting", total_len, csio->dxfer_len);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
//ISCSI_SESSION_DEBUG(is, "r2t; off %zd, len %zd", off, total_len);
|
|
|
|
for (;;) {
|
|
len = total_len;
|
|
|
|
if (len > is->is_max_send_data_segment_length)
|
|
len = is->is_max_send_data_segment_length;
|
|
|
|
if (off + len > csio->dxfer_len) {
|
|
ISCSI_SESSION_WARN(is, "target requested invalid "
|
|
"length/offset %zd, buffer is %d; reconnecting",
|
|
off + len, csio->dxfer_len);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
request = icl_pdu_new(response->ip_conn, M_NOWAIT);
|
|
if (request == NULL) {
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs;
|
|
bhsdo->bhsdo_opcode = ISCSI_BHS_OPCODE_SCSI_DATA_OUT;
|
|
bhsdo->bhsdo_lun = bhsr2t->bhsr2t_lun;
|
|
bhsdo->bhsdo_initiator_task_tag =
|
|
bhsr2t->bhsr2t_initiator_task_tag;
|
|
bhsdo->bhsdo_target_transfer_tag =
|
|
bhsr2t->bhsr2t_target_transfer_tag;
|
|
bhsdo->bhsdo_datasn = htonl(io->io_datasn++);
|
|
bhsdo->bhsdo_buffer_offset = htonl(off);
|
|
error = icl_pdu_append_data(request, csio->data_ptr + off, len,
|
|
M_NOWAIT);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate memory; "
|
|
"reconnecting");
|
|
icl_pdu_free(request);
|
|
icl_pdu_free(response);
|
|
iscsi_session_reconnect(is);
|
|
return;
|
|
}
|
|
|
|
off += len;
|
|
total_len -= len;
|
|
|
|
if (total_len == 0) {
|
|
bhsdo->bhsdo_flags |= BHSDO_FLAGS_F;
|
|
//ISCSI_SESSION_DEBUG(is, "setting F, off %zd", off);
|
|
} else {
|
|
//ISCSI_SESSION_DEBUG(is, "not finished, off %zd", off);
|
|
}
|
|
|
|
iscsi_pdu_queue_locked(request);
|
|
|
|
if (total_len == 0)
|
|
break;
|
|
}
|
|
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_async_message(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_bhs_asynchronous_message *bhsam;
|
|
struct iscsi_session *is;
|
|
|
|
is = PDU_SESSION(response);
|
|
bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs;
|
|
switch (bhsam->bhsam_async_event) {
|
|
case BHSAM_EVENT_TARGET_REQUESTS_LOGOUT:
|
|
ISCSI_SESSION_WARN(is, "target requests logout; removing session");
|
|
iscsi_session_logout(is);
|
|
iscsi_session_terminate(is);
|
|
break;
|
|
case BHSAM_EVENT_TARGET_TERMINATES_CONNECTION:
|
|
ISCSI_SESSION_WARN(is, "target indicates it will drop drop the connection");
|
|
break;
|
|
case BHSAM_EVENT_TARGET_TERMINATES_SESSION:
|
|
ISCSI_SESSION_WARN(is, "target indicates it will drop drop the session");
|
|
break;
|
|
default:
|
|
/*
|
|
* XXX: Technically, we're obligated to also handle
|
|
* parameter renegotiation.
|
|
*/
|
|
ISCSI_SESSION_WARN(is, "ignoring AsyncEvent %d", bhsam->bhsam_async_event);
|
|
break;
|
|
}
|
|
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static void
|
|
iscsi_pdu_handle_reject(struct icl_pdu *response)
|
|
{
|
|
struct iscsi_bhs_reject *bhsr;
|
|
struct iscsi_session *is;
|
|
|
|
is = PDU_SESSION(response);
|
|
bhsr = (struct iscsi_bhs_reject *)response->ip_bhs;
|
|
ISCSI_SESSION_WARN(is, "received Reject PDU, reason 0x%x; protocol error?",
|
|
bhsr->bhsr_reason);
|
|
|
|
icl_pdu_free(response);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_daemon_wait(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_request *request)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct icl_drv_limits idl;
|
|
int error;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
for (;;) {
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
ISCSI_SESSION_LOCK(is);
|
|
if (is->is_conf.isc_enable == 0 &&
|
|
is->is_conf.isc_discovery == 0) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
continue;
|
|
}
|
|
if (is->is_waiting_for_iscsid)
|
|
break;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
|
|
if (is == NULL) {
|
|
/*
|
|
* No session requires attention from iscsid(8); wait.
|
|
*/
|
|
error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock);
|
|
if (error != 0) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (error);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
is->is_waiting_for_iscsid = false;
|
|
is->is_login_phase = true;
|
|
is->is_reason[0] = '\0';
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
request->idr_session_id = is->is_id;
|
|
memcpy(&request->idr_isid, &is->is_isid,
|
|
sizeof(request->idr_isid));
|
|
request->idr_tsih = 0; /* New or reinstated session. */
|
|
memcpy(&request->idr_conf, &is->is_conf,
|
|
sizeof(request->idr_conf));
|
|
|
|
error = icl_limits(is->is_conf.isc_offload,
|
|
is->is_conf.isc_iser, &idl);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is, "icl_limits for offload \"%s\" "
|
|
"failed with error %d", is->is_conf.isc_offload,
|
|
error);
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (error);
|
|
}
|
|
request->idr_limits.isl_max_recv_data_segment_length =
|
|
idl.idl_max_recv_data_segment_length;
|
|
request->idr_limits.isl_max_send_data_segment_length =
|
|
idl.idl_max_send_data_segment_length;
|
|
request->idr_limits.isl_max_burst_length =
|
|
idl.idl_max_burst_length;
|
|
request->idr_limits.isl_first_burst_length =
|
|
idl.idl_first_burst_length;
|
|
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_daemon_handoff(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_handoff *handoff)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct icl_conn *ic;
|
|
int error;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
|
|
/*
|
|
* Find the session to hand off socket to.
|
|
*/
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (is->is_id == handoff->idh_session_id)
|
|
break;
|
|
}
|
|
if (is == NULL) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
ISCSI_SESSION_LOCK(is);
|
|
ic = is->is_conn;
|
|
if (is->is_conf.isc_discovery || is->is_terminating) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (EINVAL);
|
|
}
|
|
if (is->is_connected) {
|
|
/*
|
|
* This might have happened because another iscsid(8)
|
|
* instance handed off the connection in the meantime.
|
|
* Just return.
|
|
*/
|
|
ISCSI_SESSION_WARN(is, "handoff on already connected "
|
|
"session");
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (EBUSY);
|
|
}
|
|
|
|
strlcpy(is->is_target_alias, handoff->idh_target_alias,
|
|
sizeof(is->is_target_alias));
|
|
is->is_tsih = handoff->idh_tsih;
|
|
is->is_statsn = handoff->idh_statsn;
|
|
is->is_initial_r2t = handoff->idh_initial_r2t;
|
|
is->is_immediate_data = handoff->idh_immediate_data;
|
|
|
|
is->is_max_recv_data_segment_length =
|
|
handoff->idh_max_recv_data_segment_length;
|
|
is->is_max_send_data_segment_length =
|
|
handoff->idh_max_send_data_segment_length;
|
|
is->is_max_burst_length = handoff->idh_max_burst_length;
|
|
is->is_first_burst_length = handoff->idh_first_burst_length;
|
|
|
|
if (handoff->idh_header_digest == ISCSI_DIGEST_CRC32C)
|
|
ic->ic_header_crc32c = true;
|
|
else
|
|
ic->ic_header_crc32c = false;
|
|
if (handoff->idh_data_digest == ISCSI_DIGEST_CRC32C)
|
|
ic->ic_data_crc32c = true;
|
|
else
|
|
ic->ic_data_crc32c = false;
|
|
ic->ic_maxtags = maxtags;
|
|
|
|
is->is_cmdsn = 0;
|
|
is->is_expcmdsn = 0;
|
|
is->is_maxcmdsn = 0;
|
|
is->is_waiting_for_iscsid = false;
|
|
is->is_login_phase = false;
|
|
is->is_timeout = 0;
|
|
is->is_connected = true;
|
|
is->is_reason[0] = '\0';
|
|
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
/*
|
|
* If we're going through the proxy, the idh_socket will be 0,
|
|
* and the ICL module can simply ignore this call. It can also
|
|
* use it to determine it's no longer in the Login phase.
|
|
*/
|
|
error = icl_conn_handoff(ic, handoff->idh_socket);
|
|
if (error != 0) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
iscsi_session_terminate(is);
|
|
return (error);
|
|
}
|
|
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
if (is->is_sim != NULL) {
|
|
/*
|
|
* When reconnecting, there already is SIM allocated for the session.
|
|
*/
|
|
KASSERT(is->is_simq_frozen, ("reconnect without frozen simq"));
|
|
ISCSI_SESSION_LOCK(is);
|
|
ISCSI_SESSION_DEBUG(is, "releasing");
|
|
xpt_release_simq(is->is_sim, 1);
|
|
is->is_simq_frozen = false;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
} else {
|
|
ISCSI_SESSION_LOCK(is);
|
|
is->is_devq = cam_simq_alloc(ic->ic_maxtags);
|
|
if (is->is_devq == NULL) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate simq");
|
|
iscsi_session_terminate(is);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
is->is_sim = cam_sim_alloc(iscsi_action, iscsi_poll, "iscsi",
|
|
is, is->is_id /* unit */, &is->is_lock,
|
|
1, ic->ic_maxtags, is->is_devq);
|
|
if (is->is_sim == NULL) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
ISCSI_SESSION_WARN(is, "failed to allocate SIM");
|
|
cam_simq_free(is->is_devq);
|
|
iscsi_session_terminate(is);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
error = xpt_bus_register(is->is_sim, NULL, 0);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
ISCSI_SESSION_WARN(is, "failed to register bus");
|
|
iscsi_session_terminate(is);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
error = xpt_create_path(&is->is_path, /*periph*/NULL,
|
|
cam_sim_path(is->is_sim), CAM_TARGET_WILDCARD,
|
|
CAM_LUN_WILDCARD);
|
|
if (error != CAM_REQ_CMP) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
ISCSI_SESSION_WARN(is, "failed to create path");
|
|
iscsi_session_terminate(is);
|
|
return (ENOMEM);
|
|
}
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_daemon_fail(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_fail *fail)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (is->is_id == fail->idf_session_id)
|
|
break;
|
|
}
|
|
if (is == NULL) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
ISCSI_SESSION_LOCK(is);
|
|
ISCSI_SESSION_DEBUG(is, "iscsid(8) failed: %s",
|
|
fail->idf_reason);
|
|
strlcpy(is->is_reason, fail->idf_reason, sizeof(is->is_reason));
|
|
//is->is_waiting_for_iscsid = false;
|
|
//is->is_login_phase = true;
|
|
//iscsi_session_reconnect(is);
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
return (0);
|
|
}
|
|
|
|
#ifdef ICL_KERNEL_PROXY
|
|
static int
|
|
iscsi_ioctl_daemon_connect(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_connect *idc)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct sockaddr *from_sa, *to_sa;
|
|
int error;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (is->is_id == idc->idc_session_id)
|
|
break;
|
|
}
|
|
if (is == NULL) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
if (idc->idc_from_addrlen > 0) {
|
|
error = getsockaddr(&from_sa, (void *)idc->idc_from_addr, idc->idc_from_addrlen);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is,
|
|
"getsockaddr failed with error %d", error);
|
|
return (error);
|
|
}
|
|
} else {
|
|
from_sa = NULL;
|
|
}
|
|
error = getsockaddr(&to_sa, (void *)idc->idc_to_addr, idc->idc_to_addrlen);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is, "getsockaddr failed with error %d",
|
|
error);
|
|
free(from_sa, M_SONAME);
|
|
return (error);
|
|
}
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
is->is_statsn = 0;
|
|
is->is_cmdsn = 0;
|
|
is->is_expcmdsn = 0;
|
|
is->is_maxcmdsn = 0;
|
|
is->is_waiting_for_iscsid = false;
|
|
is->is_login_phase = true;
|
|
is->is_timeout = 0;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
error = icl_conn_connect(is->is_conn, idc->idc_domain,
|
|
idc->idc_socktype, idc->idc_protocol, from_sa, to_sa);
|
|
free(from_sa, M_SONAME);
|
|
free(to_sa, M_SONAME);
|
|
|
|
/*
|
|
* Digests are always disabled during login phase.
|
|
*/
|
|
is->is_conn->ic_header_crc32c = false;
|
|
is->is_conn->ic_data_crc32c = false;
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_daemon_send(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_send *ids)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct icl_pdu *ip;
|
|
size_t datalen;
|
|
void *data;
|
|
int error;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (is->is_id == ids->ids_session_id)
|
|
break;
|
|
}
|
|
if (is == NULL) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
if (is->is_login_phase == false)
|
|
return (EBUSY);
|
|
|
|
if (is->is_terminating || is->is_reconnecting)
|
|
return (EIO);
|
|
|
|
datalen = ids->ids_data_segment_len;
|
|
if (datalen > is->is_max_send_data_segment_length)
|
|
return (EINVAL);
|
|
if (datalen > 0) {
|
|
data = malloc(datalen, M_ISCSI, M_WAITOK);
|
|
error = copyin(ids->ids_data_segment, data, datalen);
|
|
if (error != 0) {
|
|
free(data, M_ISCSI);
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
ip = icl_pdu_new(is->is_conn, M_WAITOK);
|
|
memcpy(ip->ip_bhs, ids->ids_bhs, sizeof(*ip->ip_bhs));
|
|
if (datalen > 0) {
|
|
error = icl_pdu_append_data(ip, data, datalen, M_WAITOK);
|
|
KASSERT(error == 0, ("icl_pdu_append_data(..., M_WAITOK) failed"));
|
|
free(data, M_ISCSI);
|
|
}
|
|
iscsi_pdu_queue(ip);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_daemon_receive(struct iscsi_softc *sc,
|
|
struct iscsi_daemon_receive *idr)
|
|
{
|
|
struct iscsi_session *is;
|
|
struct icl_pdu *ip;
|
|
void *data;
|
|
int error;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (is->is_id == idr->idr_session_id)
|
|
break;
|
|
}
|
|
if (is == NULL) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
if (is->is_login_phase == false)
|
|
return (EBUSY);
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
while (is->is_login_pdu == NULL &&
|
|
is->is_terminating == false &&
|
|
is->is_reconnecting == false) {
|
|
error = cv_wait_sig(&is->is_login_cv, &is->is_lock);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return (error);
|
|
}
|
|
}
|
|
if (is->is_terminating || is->is_reconnecting) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
return (EIO);
|
|
}
|
|
ip = is->is_login_pdu;
|
|
is->is_login_pdu = NULL;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
if (ip->ip_data_len > idr->idr_data_segment_len) {
|
|
icl_pdu_free(ip);
|
|
return (EMSGSIZE);
|
|
}
|
|
|
|
copyout(ip->ip_bhs, idr->idr_bhs, sizeof(*ip->ip_bhs));
|
|
if (ip->ip_data_len > 0) {
|
|
data = malloc(ip->ip_data_len, M_ISCSI, M_WAITOK);
|
|
icl_pdu_get_data(ip, 0, data, ip->ip_data_len);
|
|
copyout(data, idr->idr_data_segment, ip->ip_data_len);
|
|
free(data, M_ISCSI);
|
|
}
|
|
|
|
icl_pdu_free(ip);
|
|
|
|
return (0);
|
|
}
|
|
#endif /* ICL_KERNEL_PROXY */
|
|
|
|
static void
|
|
iscsi_sanitize_session_conf(struct iscsi_session_conf *isc)
|
|
{
|
|
/*
|
|
* Just make sure all the fields are null-terminated.
|
|
*
|
|
* XXX: This is not particularly secure. We should
|
|
* create our own conf and then copy in relevant
|
|
* fields.
|
|
*/
|
|
isc->isc_initiator[ISCSI_NAME_LEN - 1] = '\0';
|
|
isc->isc_initiator_addr[ISCSI_ADDR_LEN - 1] = '\0';
|
|
isc->isc_initiator_alias[ISCSI_ALIAS_LEN - 1] = '\0';
|
|
isc->isc_target[ISCSI_NAME_LEN - 1] = '\0';
|
|
isc->isc_target_addr[ISCSI_ADDR_LEN - 1] = '\0';
|
|
isc->isc_user[ISCSI_NAME_LEN - 1] = '\0';
|
|
isc->isc_secret[ISCSI_SECRET_LEN - 1] = '\0';
|
|
isc->isc_mutual_user[ISCSI_NAME_LEN - 1] = '\0';
|
|
isc->isc_mutual_secret[ISCSI_SECRET_LEN - 1] = '\0';
|
|
}
|
|
|
|
static bool
|
|
iscsi_valid_session_conf(const struct iscsi_session_conf *isc)
|
|
{
|
|
|
|
if (isc->isc_initiator[0] == '\0') {
|
|
ISCSI_DEBUG("empty isc_initiator");
|
|
return (false);
|
|
}
|
|
|
|
if (isc->isc_target_addr[0] == '\0') {
|
|
ISCSI_DEBUG("empty isc_target_addr");
|
|
return (false);
|
|
}
|
|
|
|
if (isc->isc_discovery != 0 && isc->isc_target[0] != 0) {
|
|
ISCSI_DEBUG("non-empty isc_target for discovery session");
|
|
return (false);
|
|
}
|
|
|
|
if (isc->isc_discovery == 0 && isc->isc_target[0] == 0) {
|
|
ISCSI_DEBUG("empty isc_target for non-discovery session");
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_session_add(struct iscsi_softc *sc, struct iscsi_session_add *isa)
|
|
{
|
|
struct iscsi_session *is;
|
|
const struct iscsi_session *is2;
|
|
int error;
|
|
|
|
iscsi_sanitize_session_conf(&isa->isa_conf);
|
|
if (iscsi_valid_session_conf(&isa->isa_conf) == false)
|
|
return (EINVAL);
|
|
|
|
is = malloc(sizeof(*is), M_ISCSI, M_ZERO | M_WAITOK);
|
|
memcpy(&is->is_conf, &isa->isa_conf, sizeof(is->is_conf));
|
|
|
|
sx_xlock(&sc->sc_lock);
|
|
|
|
/*
|
|
* Prevent duplicates.
|
|
*/
|
|
TAILQ_FOREACH(is2, &sc->sc_sessions, is_next) {
|
|
if (!!is->is_conf.isc_discovery !=
|
|
!!is2->is_conf.isc_discovery)
|
|
continue;
|
|
|
|
if (strcmp(is->is_conf.isc_target_addr,
|
|
is2->is_conf.isc_target_addr) != 0)
|
|
continue;
|
|
|
|
if (is->is_conf.isc_discovery == 0 &&
|
|
strcmp(is->is_conf.isc_target,
|
|
is2->is_conf.isc_target) != 0)
|
|
continue;
|
|
|
|
sx_xunlock(&sc->sc_lock);
|
|
free(is, M_ISCSI);
|
|
return (EBUSY);
|
|
}
|
|
|
|
is->is_conn = icl_new_conn(is->is_conf.isc_offload,
|
|
is->is_conf.isc_iser, "iscsi", &is->is_lock);
|
|
if (is->is_conn == NULL) {
|
|
sx_xunlock(&sc->sc_lock);
|
|
free(is, M_ISCSI);
|
|
return (EINVAL);
|
|
}
|
|
is->is_conn->ic_receive = iscsi_receive_callback;
|
|
is->is_conn->ic_error = iscsi_error_callback;
|
|
is->is_conn->ic_prv0 = is;
|
|
TAILQ_INIT(&is->is_outstanding);
|
|
STAILQ_INIT(&is->is_postponed);
|
|
mtx_init(&is->is_lock, "iscsi_lock", NULL, MTX_DEF);
|
|
cv_init(&is->is_maintenance_cv, "iscsi_mt");
|
|
#ifdef ICL_KERNEL_PROXY
|
|
cv_init(&is->is_login_cv, "iscsi_login");
|
|
#endif
|
|
|
|
is->is_softc = sc;
|
|
sc->sc_last_session_id++;
|
|
is->is_id = sc->sc_last_session_id;
|
|
is->is_isid[0] = 0x80; /* RFC 3720, 10.12.5: 10b, "Random" ISID. */
|
|
arc4rand(&is->is_isid[1], 5, 0);
|
|
is->is_tsih = 0;
|
|
callout_init(&is->is_callout, 1);
|
|
|
|
error = kthread_add(iscsi_maintenance_thread, is, NULL, NULL, 0, 0, "iscsimt");
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is, "kthread_add(9) failed with error %d", error);
|
|
sx_xunlock(&sc->sc_lock);
|
|
return (error);
|
|
}
|
|
|
|
callout_reset(&is->is_callout, 1 * hz, iscsi_callout, is);
|
|
TAILQ_INSERT_TAIL(&sc->sc_sessions, is, is_next);
|
|
|
|
ISCSI_SESSION_LOCK(is);
|
|
/*
|
|
* Don't notify iscsid(8) if the session is disabled and it's not
|
|
* a discovery session,
|
|
*/
|
|
if (is->is_conf.isc_enable == 0 && is->is_conf.isc_discovery == 0) {
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
sx_xunlock(&sc->sc_lock);
|
|
return (0);
|
|
}
|
|
|
|
is->is_waiting_for_iscsid = true;
|
|
strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason));
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
cv_signal(&sc->sc_cv);
|
|
sx_xunlock(&sc->sc_lock);
|
|
return (0);
|
|
}
|
|
|
|
static bool
|
|
iscsi_session_conf_matches(unsigned int id1, const struct iscsi_session_conf *c1,
|
|
unsigned int id2, const struct iscsi_session_conf *c2)
|
|
{
|
|
|
|
if (id2 != 0 && id2 != id1)
|
|
return (false);
|
|
if (c2->isc_target[0] != '\0' &&
|
|
strcmp(c1->isc_target, c2->isc_target) != 0)
|
|
return (false);
|
|
if (c2->isc_target_addr[0] != '\0' &&
|
|
strcmp(c1->isc_target_addr, c2->isc_target_addr) != 0)
|
|
return (false);
|
|
return (true);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_session_remove(struct iscsi_softc *sc,
|
|
struct iscsi_session_remove *isr)
|
|
{
|
|
struct iscsi_session *is, *tmp;
|
|
bool found = false;
|
|
|
|
iscsi_sanitize_session_conf(&isr->isr_conf);
|
|
|
|
sx_xlock(&sc->sc_lock);
|
|
TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) {
|
|
ISCSI_SESSION_LOCK(is);
|
|
if (iscsi_session_conf_matches(is->is_id, &is->is_conf,
|
|
isr->isr_session_id, &isr->isr_conf)) {
|
|
found = true;
|
|
iscsi_session_logout(is);
|
|
iscsi_session_terminate(is);
|
|
}
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
sx_xunlock(&sc->sc_lock);
|
|
|
|
if (!found)
|
|
return (ESRCH);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_session_list(struct iscsi_softc *sc, struct iscsi_session_list *isl)
|
|
{
|
|
int error;
|
|
unsigned int i = 0;
|
|
struct iscsi_session *is;
|
|
struct iscsi_session_state iss;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
if (i >= isl->isl_nentries) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (EMSGSIZE);
|
|
}
|
|
memset(&iss, 0, sizeof(iss));
|
|
memcpy(&iss.iss_conf, &is->is_conf, sizeof(iss.iss_conf));
|
|
iss.iss_id = is->is_id;
|
|
strlcpy(iss.iss_target_alias, is->is_target_alias, sizeof(iss.iss_target_alias));
|
|
strlcpy(iss.iss_reason, is->is_reason, sizeof(iss.iss_reason));
|
|
strlcpy(iss.iss_offload, is->is_conn->ic_offload, sizeof(iss.iss_offload));
|
|
|
|
if (is->is_conn->ic_header_crc32c)
|
|
iss.iss_header_digest = ISCSI_DIGEST_CRC32C;
|
|
else
|
|
iss.iss_header_digest = ISCSI_DIGEST_NONE;
|
|
|
|
if (is->is_conn->ic_data_crc32c)
|
|
iss.iss_data_digest = ISCSI_DIGEST_CRC32C;
|
|
else
|
|
iss.iss_data_digest = ISCSI_DIGEST_NONE;
|
|
|
|
iss.iss_max_send_data_segment_length =
|
|
is->is_max_send_data_segment_length;
|
|
iss.iss_max_recv_data_segment_length =
|
|
is->is_max_recv_data_segment_length;
|
|
iss.iss_max_burst_length = is->is_max_burst_length;
|
|
iss.iss_first_burst_length = is->is_first_burst_length;
|
|
iss.iss_immediate_data = is->is_immediate_data;
|
|
iss.iss_connected = is->is_connected;
|
|
|
|
error = copyout(&iss, isl->isl_pstates + i, sizeof(iss));
|
|
if (error != 0) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (error);
|
|
}
|
|
i++;
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
|
|
isl->isl_nentries = i;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl_session_modify(struct iscsi_softc *sc,
|
|
struct iscsi_session_modify *ism)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
iscsi_sanitize_session_conf(&ism->ism_conf);
|
|
if (iscsi_valid_session_conf(&ism->ism_conf) == false)
|
|
return (EINVAL);
|
|
|
|
sx_xlock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
ISCSI_SESSION_LOCK(is);
|
|
if (is->is_id == ism->ism_session_id)
|
|
break;
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
if (is == NULL) {
|
|
sx_xunlock(&sc->sc_lock);
|
|
return (ESRCH);
|
|
}
|
|
sx_xunlock(&sc->sc_lock);
|
|
|
|
memcpy(&is->is_conf, &ism->ism_conf, sizeof(is->is_conf));
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
|
|
iscsi_session_reconnect(is);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode,
|
|
struct thread *td)
|
|
{
|
|
struct iscsi_softc *sc;
|
|
|
|
sc = dev->si_drv1;
|
|
|
|
switch (cmd) {
|
|
case ISCSIDWAIT:
|
|
return (iscsi_ioctl_daemon_wait(sc,
|
|
(struct iscsi_daemon_request *)arg));
|
|
case ISCSIDHANDOFF:
|
|
return (iscsi_ioctl_daemon_handoff(sc,
|
|
(struct iscsi_daemon_handoff *)arg));
|
|
case ISCSIDFAIL:
|
|
return (iscsi_ioctl_daemon_fail(sc,
|
|
(struct iscsi_daemon_fail *)arg));
|
|
#ifdef ICL_KERNEL_PROXY
|
|
case ISCSIDCONNECT:
|
|
return (iscsi_ioctl_daemon_connect(sc,
|
|
(struct iscsi_daemon_connect *)arg));
|
|
case ISCSIDSEND:
|
|
return (iscsi_ioctl_daemon_send(sc,
|
|
(struct iscsi_daemon_send *)arg));
|
|
case ISCSIDRECEIVE:
|
|
return (iscsi_ioctl_daemon_receive(sc,
|
|
(struct iscsi_daemon_receive *)arg));
|
|
#endif /* ICL_KERNEL_PROXY */
|
|
case ISCSISADD:
|
|
return (iscsi_ioctl_session_add(sc,
|
|
(struct iscsi_session_add *)arg));
|
|
case ISCSISREMOVE:
|
|
return (iscsi_ioctl_session_remove(sc,
|
|
(struct iscsi_session_remove *)arg));
|
|
case ISCSISLIST:
|
|
return (iscsi_ioctl_session_list(sc,
|
|
(struct iscsi_session_list *)arg));
|
|
case ISCSISMODIFY:
|
|
return (iscsi_ioctl_session_modify(sc,
|
|
(struct iscsi_session_modify *)arg));
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
static struct iscsi_outstanding *
|
|
iscsi_outstanding_find(struct iscsi_session *is, uint32_t initiator_task_tag)
|
|
{
|
|
struct iscsi_outstanding *io;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
TAILQ_FOREACH(io, &is->is_outstanding, io_next) {
|
|
if (io->io_initiator_task_tag == initiator_task_tag)
|
|
return (io);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static struct iscsi_outstanding *
|
|
iscsi_outstanding_find_ccb(struct iscsi_session *is, union ccb *ccb)
|
|
{
|
|
struct iscsi_outstanding *io;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
TAILQ_FOREACH(io, &is->is_outstanding, io_next) {
|
|
if (io->io_ccb == ccb)
|
|
return (io);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static struct iscsi_outstanding *
|
|
iscsi_outstanding_add(struct iscsi_session *is, struct icl_pdu *request,
|
|
union ccb *ccb, uint32_t *initiator_task_tagp)
|
|
{
|
|
struct iscsi_outstanding *io;
|
|
int error;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
io = uma_zalloc(iscsi_outstanding_zone, M_NOWAIT | M_ZERO);
|
|
if (io == NULL) {
|
|
ISCSI_SESSION_WARN(is, "failed to allocate %zd bytes",
|
|
sizeof(*io));
|
|
return (NULL);
|
|
}
|
|
|
|
error = icl_conn_task_setup(is->is_conn, request, &ccb->csio,
|
|
initiator_task_tagp, &io->io_icl_prv);
|
|
if (error != 0) {
|
|
ISCSI_SESSION_WARN(is,
|
|
"icl_conn_task_setup() failed with error %d", error);
|
|
uma_zfree(iscsi_outstanding_zone, io);
|
|
return (NULL);
|
|
}
|
|
|
|
KASSERT(iscsi_outstanding_find(is, *initiator_task_tagp) == NULL,
|
|
("initiator_task_tag 0x%x already added", *initiator_task_tagp));
|
|
|
|
io->io_initiator_task_tag = *initiator_task_tagp;
|
|
io->io_ccb = ccb;
|
|
TAILQ_INSERT_TAIL(&is->is_outstanding, io, io_next);
|
|
return (io);
|
|
}
|
|
|
|
static void
|
|
iscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io)
|
|
{
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
icl_conn_task_done(is->is_conn, io->io_icl_prv);
|
|
TAILQ_REMOVE(&is->is_outstanding, io, io_next);
|
|
uma_zfree(iscsi_outstanding_zone, io);
|
|
}
|
|
|
|
static void
|
|
iscsi_action_abort(struct iscsi_session *is, union ccb *ccb)
|
|
{
|
|
struct icl_pdu *request;
|
|
struct iscsi_bhs_task_management_request *bhstmr;
|
|
struct ccb_abort *cab = &ccb->cab;
|
|
struct iscsi_outstanding *io, *aio;
|
|
uint32_t initiator_task_tag;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
#if 0
|
|
KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__));
|
|
#else
|
|
if (is->is_login_phase) {
|
|
ccb->ccb_h.status = CAM_REQ_ABORTED;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
aio = iscsi_outstanding_find_ccb(is, cab->abort_ccb);
|
|
if (aio == NULL) {
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
request = icl_pdu_new(is->is_conn, M_NOWAIT);
|
|
if (request == NULL) {
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
initiator_task_tag = is->is_initiator_task_tag++;
|
|
|
|
io = iscsi_outstanding_add(is, request, NULL, &initiator_task_tag);
|
|
if (io == NULL) {
|
|
icl_pdu_free(request);
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
io->io_datasn = aio->io_initiator_task_tag;
|
|
|
|
bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs;
|
|
bhstmr->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_REQUEST;
|
|
bhstmr->bhstmr_function = 0x80 | BHSTMR_FUNCTION_ABORT_TASK;
|
|
bhstmr->bhstmr_lun = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun));
|
|
bhstmr->bhstmr_initiator_task_tag = initiator_task_tag;
|
|
bhstmr->bhstmr_referenced_task_tag = aio->io_initiator_task_tag;
|
|
|
|
iscsi_pdu_queue_locked(request);
|
|
}
|
|
|
|
static void
|
|
iscsi_action_scsiio(struct iscsi_session *is, union ccb *ccb)
|
|
{
|
|
struct icl_pdu *request;
|
|
struct iscsi_bhs_scsi_command *bhssc;
|
|
struct ccb_scsiio *csio;
|
|
struct iscsi_outstanding *io;
|
|
size_t len;
|
|
uint32_t initiator_task_tag;
|
|
int error;
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
#if 0
|
|
KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__));
|
|
#else
|
|
if (is->is_login_phase) {
|
|
ISCSI_SESSION_DEBUG(is, "called during login phase");
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_REQ_ABORTED | CAM_DEV_QFRZN;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
request = icl_pdu_new(is->is_conn, M_NOWAIT);
|
|
if (request == NULL) {
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
initiator_task_tag = is->is_initiator_task_tag++;
|
|
io = iscsi_outstanding_add(is, request, ccb, &initiator_task_tag);
|
|
if (io == NULL) {
|
|
icl_pdu_free(request);
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
csio = &ccb->csio;
|
|
bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs;
|
|
bhssc->bhssc_opcode = ISCSI_BHS_OPCODE_SCSI_COMMAND;
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_F;
|
|
switch (csio->ccb_h.flags & CAM_DIR_MASK) {
|
|
case CAM_DIR_IN:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_R;
|
|
break;
|
|
case CAM_DIR_OUT:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_W;
|
|
break;
|
|
}
|
|
|
|
if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) {
|
|
switch (csio->tag_action) {
|
|
case MSG_HEAD_OF_Q_TAG:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_HOQ;
|
|
break;
|
|
case MSG_ORDERED_Q_TAG:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ORDERED;
|
|
break;
|
|
case MSG_ACA_TASK:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ACA;
|
|
break;
|
|
case MSG_SIMPLE_Q_TAG:
|
|
default:
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_SIMPLE;
|
|
break;
|
|
}
|
|
} else
|
|
bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_UNTAGGED;
|
|
|
|
bhssc->bhssc_lun = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun));
|
|
bhssc->bhssc_initiator_task_tag = initiator_task_tag;
|
|
bhssc->bhssc_expected_data_transfer_length = htonl(csio->dxfer_len);
|
|
KASSERT(csio->cdb_len <= sizeof(bhssc->bhssc_cdb),
|
|
("unsupported CDB size %zd", (size_t)csio->cdb_len));
|
|
|
|
if (csio->ccb_h.flags & CAM_CDB_POINTER)
|
|
memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_ptr, csio->cdb_len);
|
|
else
|
|
memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_bytes, csio->cdb_len);
|
|
|
|
if (is->is_immediate_data &&
|
|
(csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
|
|
len = csio->dxfer_len;
|
|
//ISCSI_SESSION_DEBUG(is, "adding %zd of immediate data", len);
|
|
if (len > is->is_first_burst_length) {
|
|
ISCSI_SESSION_DEBUG(is, "len %zd -> %d", len, is->is_first_burst_length);
|
|
len = is->is_first_burst_length;
|
|
}
|
|
if (len > is->is_max_send_data_segment_length) {
|
|
ISCSI_SESSION_DEBUG(is, "len %zd -> %d", len,
|
|
is->is_max_send_data_segment_length);
|
|
len = is->is_max_send_data_segment_length;
|
|
}
|
|
|
|
error = icl_pdu_append_data(request, csio->data_ptr, len, M_NOWAIT);
|
|
if (error != 0) {
|
|
iscsi_outstanding_remove(is, io);
|
|
icl_pdu_free(request);
|
|
if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
ISCSI_SESSION_DEBUG(is, "freezing devq");
|
|
}
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
}
|
|
iscsi_pdu_queue_locked(request);
|
|
}
|
|
|
|
static void
|
|
iscsi_action(struct cam_sim *sim, union ccb *ccb)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
is = cam_sim_softc(sim);
|
|
|
|
ISCSI_SESSION_LOCK_ASSERT(is);
|
|
|
|
if (is->is_terminating ||
|
|
(is->is_connected == false && fail_on_disconnection)) {
|
|
ccb->ccb_h.status = CAM_DEV_NOT_THERE;
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
switch (ccb->ccb_h.func_code) {
|
|
case XPT_PATH_INQ:
|
|
{
|
|
struct ccb_pathinq *cpi = &ccb->cpi;
|
|
|
|
cpi->version_num = 1;
|
|
cpi->hba_inquiry = PI_TAG_ABLE;
|
|
cpi->target_sprt = 0;
|
|
cpi->hba_misc = PIM_EXTLUNS;
|
|
/*
|
|
* XXX: It shouldn't ever be NULL; this could be turned
|
|
* into a KASSERT eventually.
|
|
*/
|
|
if (is->is_conn == NULL)
|
|
ISCSI_WARN("NULL conn");
|
|
else if (is->is_conn->ic_unmapped)
|
|
cpi->hba_misc |= PIM_UNMAPPED;
|
|
cpi->hba_eng_cnt = 0;
|
|
cpi->max_target = 0;
|
|
/*
|
|
* Note that the variable below is only relevant for targets
|
|
* that don't claim compliance with anything above SPC2, which
|
|
* means they don't support REPORT_LUNS.
|
|
*/
|
|
cpi->max_lun = 255;
|
|
cpi->initiator_id = ~0;
|
|
strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
|
|
strlcpy(cpi->hba_vid, "iSCSI", HBA_IDLEN);
|
|
strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
|
|
cpi->unit_number = cam_sim_unit(sim);
|
|
cpi->bus_id = cam_sim_bus(sim);
|
|
cpi->base_transfer_speed = 150000; /* XXX */
|
|
cpi->transport = XPORT_ISCSI;
|
|
cpi->transport_version = 0;
|
|
cpi->protocol = PROTO_SCSI;
|
|
cpi->protocol_version = SCSI_REV_SPC3;
|
|
cpi->maxio = MAXPHYS;
|
|
cpi->ccb_h.status = CAM_REQ_CMP;
|
|
break;
|
|
}
|
|
case XPT_GET_TRAN_SETTINGS:
|
|
{
|
|
struct ccb_trans_settings *cts;
|
|
struct ccb_trans_settings_scsi *scsi;
|
|
|
|
cts = &ccb->cts;
|
|
scsi = &cts->proto_specific.scsi;
|
|
|
|
cts->protocol = PROTO_SCSI;
|
|
cts->protocol_version = SCSI_REV_SPC3;
|
|
cts->transport = XPORT_ISCSI;
|
|
cts->transport_version = 0;
|
|
scsi->valid = CTS_SCSI_VALID_TQ;
|
|
scsi->flags = CTS_SCSI_FLAGS_TAG_ENB;
|
|
cts->ccb_h.status = CAM_REQ_CMP;
|
|
break;
|
|
}
|
|
case XPT_CALC_GEOMETRY:
|
|
cam_calc_geometry(&ccb->ccg, /*extended*/1);
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
break;
|
|
#if 0
|
|
/*
|
|
* XXX: What's the point?
|
|
*/
|
|
case XPT_RESET_BUS:
|
|
case XPT_TERM_IO:
|
|
ISCSI_SESSION_DEBUG(is, "faking success for reset, abort, or term_io");
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
break;
|
|
#endif
|
|
case XPT_ABORT:
|
|
iscsi_action_abort(is, ccb);
|
|
return;
|
|
case XPT_SCSI_IO:
|
|
iscsi_action_scsiio(is, ccb);
|
|
return;
|
|
default:
|
|
#if 0
|
|
ISCSI_SESSION_DEBUG(is, "got unsupported code 0x%x", ccb->ccb_h.func_code);
|
|
#endif
|
|
ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
|
|
break;
|
|
}
|
|
xpt_done(ccb);
|
|
}
|
|
|
|
static void
|
|
iscsi_poll(struct cam_sim *sim)
|
|
{
|
|
|
|
KASSERT(0, ("%s: you're not supposed to be here", __func__));
|
|
}
|
|
|
|
static void
|
|
iscsi_terminate_sessions(struct iscsi_softc *sc)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next)
|
|
iscsi_session_terminate(is);
|
|
while(!TAILQ_EMPTY(&sc->sc_sessions)) {
|
|
ISCSI_DEBUG("waiting for sessions to terminate");
|
|
cv_wait(&sc->sc_cv, &sc->sc_lock);
|
|
}
|
|
ISCSI_DEBUG("all sessions terminated");
|
|
sx_sunlock(&sc->sc_lock);
|
|
}
|
|
|
|
static void
|
|
iscsi_shutdown_pre(struct iscsi_softc *sc)
|
|
{
|
|
struct iscsi_session *is;
|
|
|
|
if (!fail_on_shutdown)
|
|
return;
|
|
|
|
/*
|
|
* If we have any sessions waiting for reconnection, request
|
|
* maintenance thread to fail them immediately instead of waiting
|
|
* for reconnect timeout.
|
|
*
|
|
* This prevents LUNs with mounted filesystems that are supported
|
|
* by disconnected iSCSI sessions from hanging, however it will
|
|
* fail all queued BIOs.
|
|
*/
|
|
ISCSI_DEBUG("forcing failing all disconnected sessions due to shutdown");
|
|
|
|
fail_on_disconnection = 1;
|
|
|
|
sx_slock(&sc->sc_lock);
|
|
TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
|
|
ISCSI_SESSION_LOCK(is);
|
|
if (!is->is_connected) {
|
|
ISCSI_SESSION_DEBUG(is, "force failing disconnected session early");
|
|
iscsi_session_reconnect(is);
|
|
}
|
|
ISCSI_SESSION_UNLOCK(is);
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
}
|
|
|
|
static void
|
|
iscsi_shutdown_post(struct iscsi_softc *sc)
|
|
{
|
|
|
|
ISCSI_DEBUG("removing all sessions due to shutdown");
|
|
iscsi_terminate_sessions(sc);
|
|
}
|
|
|
|
static int
|
|
iscsi_load(void)
|
|
{
|
|
int error;
|
|
|
|
sc = malloc(sizeof(*sc), M_ISCSI, M_ZERO | M_WAITOK);
|
|
sx_init(&sc->sc_lock, "iscsi");
|
|
TAILQ_INIT(&sc->sc_sessions);
|
|
cv_init(&sc->sc_cv, "iscsi_cv");
|
|
|
|
iscsi_outstanding_zone = uma_zcreate("iscsi_outstanding",
|
|
sizeof(struct iscsi_outstanding), NULL, NULL, NULL, NULL,
|
|
UMA_ALIGN_PTR, 0);
|
|
|
|
error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &iscsi_cdevsw,
|
|
NULL, UID_ROOT, GID_WHEEL, 0600, "iscsi");
|
|
if (error != 0) {
|
|
ISCSI_WARN("failed to create device node, error %d", error);
|
|
return (error);
|
|
}
|
|
sc->sc_cdev->si_drv1 = sc;
|
|
|
|
sc->sc_shutdown_pre_eh = EVENTHANDLER_REGISTER(shutdown_pre_sync,
|
|
iscsi_shutdown_pre, sc, SHUTDOWN_PRI_FIRST);
|
|
/*
|
|
* shutdown_post_sync needs to run after filesystem shutdown and before
|
|
* CAM shutdown - otherwise when rebooting with an iSCSI session that is
|
|
* disconnected but has outstanding requests, dashutdown() will hang on
|
|
* cam_periph_runccb().
|
|
*/
|
|
sc->sc_shutdown_post_eh = EVENTHANDLER_REGISTER(shutdown_post_sync,
|
|
iscsi_shutdown_post, sc, SHUTDOWN_PRI_DEFAULT - 1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_unload(void)
|
|
{
|
|
|
|
if (sc->sc_cdev != NULL) {
|
|
ISCSI_DEBUG("removing device node");
|
|
destroy_dev(sc->sc_cdev);
|
|
ISCSI_DEBUG("device node removed");
|
|
}
|
|
|
|
if (sc->sc_shutdown_pre_eh != NULL)
|
|
EVENTHANDLER_DEREGISTER(shutdown_pre_sync, sc->sc_shutdown_pre_eh);
|
|
if (sc->sc_shutdown_post_eh != NULL)
|
|
EVENTHANDLER_DEREGISTER(shutdown_post_sync, sc->sc_shutdown_post_eh);
|
|
|
|
iscsi_terminate_sessions(sc);
|
|
|
|
uma_zdestroy(iscsi_outstanding_zone);
|
|
sx_destroy(&sc->sc_lock);
|
|
cv_destroy(&sc->sc_cv);
|
|
free(sc, M_ISCSI);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_quiesce(void)
|
|
{
|
|
sx_slock(&sc->sc_lock);
|
|
if (!TAILQ_EMPTY(&sc->sc_sessions)) {
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (EBUSY);
|
|
}
|
|
sx_sunlock(&sc->sc_lock);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iscsi_modevent(module_t mod, int what, void *arg)
|
|
{
|
|
int error;
|
|
|
|
switch (what) {
|
|
case MOD_LOAD:
|
|
error = iscsi_load();
|
|
break;
|
|
case MOD_UNLOAD:
|
|
error = iscsi_unload();
|
|
break;
|
|
case MOD_QUIESCE:
|
|
error = iscsi_quiesce();
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
moduledata_t iscsi_data = {
|
|
"iscsi",
|
|
iscsi_modevent,
|
|
0
|
|
};
|
|
|
|
DECLARE_MODULE(iscsi, iscsi_data, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
|
|
MODULE_DEPEND(iscsi, cam, 1, 1, 1);
|
|
MODULE_DEPEND(iscsi, icl, 1, 1, 1);
|