1255570Strasz/*-
2255570Strasz * Copyright (c) 2012 The FreeBSD Foundation
3255570Strasz * All rights reserved.
4255570Strasz *
5255570Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6255570Strasz * from the FreeBSD Foundation.
7255570Strasz *
8255570Strasz * Redistribution and use in source and binary forms, with or without
9255570Strasz * modification, are permitted provided that the following conditions
10255570Strasz * are met:
11255570Strasz * 1. Redistributions of source code must retain the above copyright
12255570Strasz *    notice, this list of conditions and the following disclaimer.
13255570Strasz * 2. Redistributions in binary form must reproduce the above copyright
14255570Strasz *    notice, this list of conditions and the following disclaimer in the
15255570Strasz *    documentation and/or other materials provided with the distribution.
16255570Strasz *
17255570Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18255570Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19255570Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20255570Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21255570Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22255570Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23255570Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24255570Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25255570Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26255570Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27255570Strasz * SUCH DAMAGE.
28255570Strasz *
29255570Strasz */
30255570Strasz
31270888Strasz#include <sys/cdefs.h>
32270888Strasz__FBSDID("$FreeBSD: stable/10/sys/dev/iscsi/iscsi.c 325226 2017-10-31 09:58:51Z avg $");
33270888Strasz
34255570Strasz#include <sys/param.h>
35255570Strasz#include <sys/condvar.h>
36255570Strasz#include <sys/conf.h>
37255570Strasz#include <sys/eventhandler.h>
38255570Strasz#include <sys/file.h>
39255570Strasz#include <sys/kernel.h>
40255570Strasz#include <sys/kthread.h>
41255570Strasz#include <sys/lock.h>
42255570Strasz#include <sys/malloc.h>
43255570Strasz#include <sys/mutex.h>
44255570Strasz#include <sys/module.h>
45255570Strasz#include <sys/sysctl.h>
46255570Strasz#include <sys/systm.h>
47255570Strasz#include <sys/sx.h>
48255570Strasz#include <vm/uma.h>
49255570Strasz
50255570Strasz#include <cam/cam.h>
51255570Strasz#include <cam/cam_ccb.h>
52255570Strasz#include <cam/cam_xpt.h>
53255570Strasz#include <cam/cam_debug.h>
54255570Strasz#include <cam/cam_sim.h>
55255570Strasz#include <cam/cam_xpt_sim.h>
56255570Strasz#include <cam/cam_xpt_periph.h>
57255570Strasz#include <cam/cam_periph.h>
58255570Strasz#include <cam/scsi/scsi_all.h>
59255570Strasz#include <cam/scsi/scsi_message.h>
60255570Strasz
61270891Strasz#include <dev/iscsi/icl.h>
62270891Strasz#include <dev/iscsi/iscsi_ioctl.h>
63270891Strasz#include <dev/iscsi/iscsi_proto.h>
64270891Strasz#include <dev/iscsi/iscsi.h>
65255570Strasz
66255570Strasz#ifdef ICL_KERNEL_PROXY
67255570Strasz#include <sys/socketvar.h>
68255570Strasz#endif
69255570Strasz
70265508Strasz#ifdef ICL_KERNEL_PROXY
71265508StraszFEATURE(iscsi_kernel_proxy, "iSCSI initiator built with ICL_KERNEL_PROXY");
72265508Strasz#endif
73265508Strasz
74255570Strasz/*
75255570Strasz * XXX: This is global so the iscsi_unload() can access it.
76255570Strasz * 	Think about how to do this properly.
77255570Strasz */
78255570Straszstatic struct iscsi_softc	*sc;
79255570Strasz
80255570StraszSYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD, 0, "iSCSI initiator");
81255570Straszstatic int debug = 1;
82255570StraszTUNABLE_INT("kern.iscsi.debug", &debug);
83265501StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RWTUN,
84265531Strasz    &debug, 0, "Enable debug messages");
85255570Straszstatic int ping_timeout = 5;
86255570StraszTUNABLE_INT("kern.iscsi.ping_timeout", &ping_timeout);
87265501StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RWTUN, &ping_timeout,
88265531Strasz    0, "Timeout for ping (NOP-Out) requests, in seconds");
89255570Straszstatic int iscsid_timeout = 60;
90255570StraszTUNABLE_INT("kern.iscsi.iscsid_timeout", &iscsid_timeout);
91265501StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, iscsid_timeout, CTLFLAG_RWTUN, &iscsid_timeout,
92265531Strasz    0, "Time to wait for iscsid(8) to handle reconnection, in seconds");
93255570Straszstatic int login_timeout = 60;
94255570StraszTUNABLE_INT("kern.iscsi.login_timeout", &login_timeout);
95265501StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, login_timeout, CTLFLAG_RWTUN, &login_timeout,
96265531Strasz    0, "Time to wait for iscsid(8) to finish Login Phase, in seconds");
97255570Straszstatic int maxtags = 255;
98255570StraszTUNABLE_INT("kern.iscsi.maxtags", &maxtags);
99265501StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, maxtags, CTLFLAG_RWTUN, &maxtags,
100265531Strasz    0, "Max number of IO requests queued");
101265523Straszstatic int fail_on_disconnection = 0;
102265523StraszTUNABLE_INT("kern.iscsi.fail_on_disconnection", &fail_on_disconnection);
103265523StraszSYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_disconnection, CTLFLAG_RWTUN,
104265523Strasz    &fail_on_disconnection, 0, "Destroy CAM SIM on connection failure");
105294708Ssmhstatic int fail_on_shutdown = 1;
106294708SsmhSYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_shutdown, CTLFLAG_RWTUN,
107294708Ssmh    &fail_on_shutdown, 0, "Fail disconnected sessions on shutdown");
108255570Strasz
109255570Straszstatic MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI initiator");
110255570Straszstatic uma_zone_t iscsi_outstanding_zone;
111255570Strasz
112255570Strasz#define	CONN_SESSION(X)	((struct iscsi_session *)X->ic_prv0)
113255570Strasz#define	PDU_SESSION(X)	(CONN_SESSION(X->ip_conn))
114255570Strasz
115265505Strasz#define	ISCSI_DEBUG(X, ...)						\
116265505Strasz	do {								\
117265505Strasz		if (debug > 1) 						\
118265505Strasz			printf("%s: " X "\n", __func__, ## __VA_ARGS__);\
119255570Strasz	} while (0)
120255570Strasz
121265505Strasz#define	ISCSI_WARN(X, ...)						\
122265505Strasz	do {								\
123265505Strasz		if (debug > 0) {					\
124265505Strasz			printf("WARNING: %s: " X "\n",			\
125265505Strasz			    __func__, ## __VA_ARGS__);			\
126265505Strasz		}							\
127255570Strasz	} while (0)
128255570Strasz
129265505Strasz#define	ISCSI_SESSION_DEBUG(S, X, ...)					\
130265505Strasz	do {								\
131265505Strasz		if (debug > 1) {					\
132265505Strasz			printf("%s: %s (%s): " X "\n",			\
133265505Strasz			    __func__, S->is_conf.isc_target_addr,	\
134265505Strasz			    S->is_conf.isc_target, ## __VA_ARGS__);	\
135265505Strasz		}							\
136255570Strasz	} while (0)
137255570Strasz
138265505Strasz#define	ISCSI_SESSION_WARN(S, X, ...)					\
139265505Strasz	do {								\
140265505Strasz		if (debug > 0) {					\
141265505Strasz			printf("WARNING: %s (%s): " X "\n",		\
142265505Strasz			    S->is_conf.isc_target_addr,			\
143265505Strasz			    S->is_conf.isc_target, ## __VA_ARGS__);	\
144265505Strasz		}							\
145255570Strasz	} while (0)
146255570Strasz
147255570Strasz#define ISCSI_SESSION_LOCK(X)		mtx_lock(&X->is_lock)
148255570Strasz#define ISCSI_SESSION_UNLOCK(X)		mtx_unlock(&X->is_lock)
149255570Strasz#define ISCSI_SESSION_LOCK_ASSERT(X)	mtx_assert(&X->is_lock, MA_OWNED)
150275559Smav#define ISCSI_SESSION_LOCK_ASSERT_NOT(X) mtx_assert(&X->is_lock, MA_NOTOWNED)
151255570Strasz
152255570Straszstatic int	iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg,
153255570Strasz		    int mode, struct thread *td);
154255570Strasz
155255570Straszstatic struct cdevsw iscsi_cdevsw = {
156255570Strasz     .d_version = D_VERSION,
157255570Strasz     .d_ioctl   = iscsi_ioctl,
158255570Strasz     .d_name    = "iscsi",
159255570Strasz};
160255570Strasz
161255570Straszstatic void	iscsi_pdu_queue_locked(struct icl_pdu *request);
162255570Straszstatic void	iscsi_pdu_queue(struct icl_pdu *request);
163255570Straszstatic void	iscsi_pdu_update_statsn(const struct icl_pdu *response);
164255570Straszstatic void	iscsi_pdu_handle_nop_in(struct icl_pdu *response);
165255570Straszstatic void	iscsi_pdu_handle_scsi_response(struct icl_pdu *response);
166268705Smavstatic void	iscsi_pdu_handle_task_response(struct icl_pdu *response);
167255570Straszstatic void	iscsi_pdu_handle_data_in(struct icl_pdu *response);
168255570Straszstatic void	iscsi_pdu_handle_logout_response(struct icl_pdu *response);
169255570Straszstatic void	iscsi_pdu_handle_r2t(struct icl_pdu *response);
170255570Straszstatic void	iscsi_pdu_handle_async_message(struct icl_pdu *response);
171255570Straszstatic void	iscsi_pdu_handle_reject(struct icl_pdu *response);
172255570Straszstatic void	iscsi_session_reconnect(struct iscsi_session *is);
173255570Straszstatic void	iscsi_session_terminate(struct iscsi_session *is);
174255570Straszstatic void	iscsi_action(struct cam_sim *sim, union ccb *ccb);
175255570Straszstatic void	iscsi_poll(struct cam_sim *sim);
176255570Straszstatic struct iscsi_outstanding	*iscsi_outstanding_find(struct iscsi_session *is,
177255570Strasz		    uint32_t initiator_task_tag);
178268705Smavstatic struct iscsi_outstanding	*iscsi_outstanding_add(struct iscsi_session *is,
179255570Strasz		    uint32_t initiator_task_tag, union ccb *ccb);
180255570Straszstatic void	iscsi_outstanding_remove(struct iscsi_session *is,
181255570Strasz		    struct iscsi_outstanding *io);
182255570Strasz
183255570Straszstatic bool
184255570Strasziscsi_pdu_prepare(struct icl_pdu *request)
185255570Strasz{
186255570Strasz	struct iscsi_session *is;
187255570Strasz	struct iscsi_bhs_scsi_command *bhssc;
188255570Strasz
189255570Strasz	is = PDU_SESSION(request);
190255570Strasz
191255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
192255570Strasz
193255570Strasz	/*
194255570Strasz	 * We're only using fields common for all the request
195255570Strasz	 * (initiator -> target) PDUs.
196255570Strasz	 */
197255570Strasz	bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs;
198255570Strasz
199255570Strasz	/*
200255570Strasz	 * Data-Out PDU does not contain CmdSN.
201255570Strasz	 */
202255570Strasz	if (bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_OUT) {
203276613Smav		if (ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn) &&
204255570Strasz		    (bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) {
205255570Strasz			/*
206255570Strasz			 * Current MaxCmdSN prevents us from sending any more
207255570Strasz			 * SCSI Command PDUs to the target; postpone the PDU.
208255570Strasz			 * It will get resent by either iscsi_pdu_queue(),
209255570Strasz			 * or by maintenance thread.
210255570Strasz			 */
211255570Strasz#if 0
212276613Smav			ISCSI_SESSION_DEBUG(is, "postponing send, CmdSN %u, "
213276613Smav			    "ExpCmdSN %u, MaxCmdSN %u, opcode 0x%x",
214276613Smav			    is->is_cmdsn, is->is_expcmdsn, is->is_maxcmdsn,
215276613Smav			    bhssc->bhssc_opcode);
216255570Strasz#endif
217255570Strasz			return (true);
218255570Strasz		}
219255570Strasz		bhssc->bhssc_cmdsn = htonl(is->is_cmdsn);
220255570Strasz		if ((bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
221255570Strasz			is->is_cmdsn++;
222255570Strasz	}
223255570Strasz	bhssc->bhssc_expstatsn = htonl(is->is_statsn + 1);
224255570Strasz
225255570Strasz	return (false);
226255570Strasz}
227255570Strasz
228255570Straszstatic void
229255570Strasziscsi_session_send_postponed(struct iscsi_session *is)
230255570Strasz{
231255570Strasz	struct icl_pdu *request;
232255570Strasz	bool postpone;
233255570Strasz
234255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
235255570Strasz
236314583Smav	if (STAILQ_EMPTY(&is->is_postponed))
237314583Smav		return;
238314583Smav	while ((request = STAILQ_FIRST(&is->is_postponed)) != NULL) {
239255570Strasz		postpone = iscsi_pdu_prepare(request);
240255570Strasz		if (postpone)
241314583Smav			return;
242265500Strasz		STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next);
243255570Strasz		icl_pdu_queue(request);
244255570Strasz	}
245314583Smav	xpt_release_simq(is->is_sim, 1);
246255570Strasz}
247255570Strasz
248255570Straszstatic void
249255570Strasziscsi_pdu_queue_locked(struct icl_pdu *request)
250255570Strasz{
251255570Strasz	struct iscsi_session *is;
252255570Strasz	bool postpone;
253255570Strasz
254255570Strasz	is = PDU_SESSION(request);
255255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
256255570Strasz	iscsi_session_send_postponed(is);
257255570Strasz	postpone = iscsi_pdu_prepare(request);
258255570Strasz	if (postpone) {
259314583Smav		if (STAILQ_EMPTY(&is->is_postponed))
260314583Smav			xpt_freeze_simq(is->is_sim, 1);
261265500Strasz		STAILQ_INSERT_TAIL(&is->is_postponed, request, ip_next);
262255570Strasz		return;
263255570Strasz	}
264255570Strasz	icl_pdu_queue(request);
265255570Strasz}
266255570Strasz
267255570Straszstatic void
268255570Strasziscsi_pdu_queue(struct icl_pdu *request)
269255570Strasz{
270255570Strasz	struct iscsi_session *is;
271255570Strasz
272255570Strasz	is = PDU_SESSION(request);
273255570Strasz	ISCSI_SESSION_LOCK(is);
274255570Strasz	iscsi_pdu_queue_locked(request);
275255570Strasz	ISCSI_SESSION_UNLOCK(is);
276255570Strasz}
277255570Strasz
278255570Straszstatic void
279255570Strasziscsi_session_logout(struct iscsi_session *is)
280255570Strasz{
281255570Strasz	struct icl_pdu *request;
282255570Strasz	struct iscsi_bhs_logout_request *bhslr;
283255570Strasz
284276618Smav	request = icl_pdu_new(is->is_conn, M_NOWAIT);
285255570Strasz	if (request == NULL)
286255570Strasz		return;
287255570Strasz
288255570Strasz	bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs;
289255570Strasz	bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST;
290255570Strasz	bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION;
291255570Strasz	iscsi_pdu_queue_locked(request);
292255570Strasz}
293255570Strasz
294255570Straszstatic void
295268705Smaviscsi_session_terminate_task(struct iscsi_session *is,
296325224Savg    struct iscsi_outstanding *io, cam_status status)
297255570Strasz{
298255570Strasz
299268705Smav	if (io->io_ccb != NULL) {
300268705Smav		io->io_ccb->ccb_h.status &= ~(CAM_SIM_QUEUED | CAM_STATUS_MASK);
301325224Savg		io->io_ccb->ccb_h.status |= status;
302255570Strasz		if ((io->io_ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
303268705Smav			io->io_ccb->ccb_h.status |= CAM_DEV_QFRZN;
304255570Strasz			xpt_freeze_devq(io->io_ccb->ccb_h.path, 1);
305255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
306255570Strasz		}
307255570Strasz		xpt_done(io->io_ccb);
308255570Strasz	}
309268705Smav	iscsi_outstanding_remove(is, io);
310255570Strasz}
311255570Strasz
312255570Straszstatic void
313325224Savgiscsi_session_terminate_tasks(struct iscsi_session *is, cam_status status)
314268705Smav{
315268705Smav	struct iscsi_outstanding *io, *tmp;
316268705Smav
317268705Smav	ISCSI_SESSION_LOCK_ASSERT(is);
318268705Smav
319268705Smav	TAILQ_FOREACH_SAFE(io, &is->is_outstanding, io_next, tmp) {
320325224Savg		iscsi_session_terminate_task(is, io, status);
321268705Smav	}
322268705Smav}
323268705Smav
324268705Smavstatic void
325265523Strasziscsi_session_cleanup(struct iscsi_session *is, bool destroy_sim)
326255570Strasz{
327255570Strasz	struct icl_pdu *pdu;
328255570Strasz
329265523Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
330255570Strasz
331255570Strasz	/*
332255570Strasz	 * Don't queue any new PDUs.
333255570Strasz	 */
334255570Strasz	if (is->is_sim != NULL && is->is_simq_frozen == false) {
335255570Strasz		ISCSI_SESSION_DEBUG(is, "freezing");
336255570Strasz		xpt_freeze_simq(is->is_sim, 1);
337255570Strasz		is->is_simq_frozen = true;
338255570Strasz	}
339255570Strasz
340255570Strasz	/*
341255570Strasz	 * Remove postponed PDUs.
342255570Strasz	 */
343314583Smav	if (!STAILQ_EMPTY(&is->is_postponed))
344314583Smav		xpt_release_simq(is->is_sim, 1);
345314583Smav	while ((pdu = STAILQ_FIRST(&is->is_postponed)) != NULL) {
346265500Strasz		STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next);
347255570Strasz		icl_pdu_free(pdu);
348255570Strasz	}
349255570Strasz
350265523Strasz	if (destroy_sim == false) {
351265523Strasz		/*
352265523Strasz		 * Terminate SCSI tasks, asking CAM to requeue them.
353265523Strasz		 */
354325224Savg		iscsi_session_terminate_tasks(is, CAM_REQUEUE_REQ);
355265523Strasz		return;
356265523Strasz	}
357255570Strasz
358325224Savg	iscsi_session_terminate_tasks(is, CAM_DEV_NOT_THERE);
359265523Strasz
360265523Strasz	if (is->is_sim == NULL)
361265523Strasz		return;
362265523Strasz
363265523Strasz	ISCSI_SESSION_DEBUG(is, "deregistering SIM");
364265523Strasz	xpt_async(AC_LOST_DEVICE, is->is_path, NULL);
365265523Strasz
366265523Strasz	if (is->is_simq_frozen) {
367265523Strasz		xpt_release_simq(is->is_sim, 1);
368265523Strasz		is->is_simq_frozen = false;
369265523Strasz	}
370265523Strasz
371265523Strasz	xpt_free_path(is->is_path);
372265523Strasz	is->is_path = NULL;
373265523Strasz	xpt_bus_deregister(cam_sim_path(is->is_sim));
374265523Strasz	cam_sim_free(is->is_sim, TRUE /*free_devq*/);
375265523Strasz	is->is_sim = NULL;
376265523Strasz	is->is_devq = NULL;
377265523Strasz}
378265523Strasz
379265523Straszstatic void
380265523Strasziscsi_maintenance_thread_reconnect(struct iscsi_session *is)
381265523Strasz{
382265523Strasz
383265523Strasz	icl_conn_close(is->is_conn);
384265523Strasz
385265523Strasz	ISCSI_SESSION_LOCK(is);
386265523Strasz
387265523Strasz	is->is_connected = false;
388265523Strasz	is->is_reconnecting = false;
389265523Strasz	is->is_login_phase = false;
390265523Strasz
391265523Strasz#ifdef ICL_KERNEL_PROXY
392265523Strasz	if (is->is_login_pdu != NULL) {
393265523Strasz		icl_pdu_free(is->is_login_pdu);
394265523Strasz		is->is_login_pdu = NULL;
395265523Strasz	}
396265523Strasz	cv_signal(&is->is_login_cv);
397265523Strasz#endif
398265523Strasz
399265523Strasz	if (fail_on_disconnection) {
400265523Strasz		ISCSI_SESSION_DEBUG(is, "connection failed, destroying devices");
401265523Strasz		iscsi_session_cleanup(is, true);
402265523Strasz	} else {
403265523Strasz		iscsi_session_cleanup(is, false);
404265523Strasz	}
405265523Strasz
406255570Strasz	KASSERT(TAILQ_EMPTY(&is->is_outstanding),
407255570Strasz	    ("destroying session with active tasks"));
408265500Strasz	KASSERT(STAILQ_EMPTY(&is->is_postponed),
409255570Strasz	    ("destroying session with postponed PDUs"));
410255570Strasz
411255570Strasz	/*
412255570Strasz	 * Request immediate reconnection from iscsid(8).
413255570Strasz	 */
414255570Strasz	//ISCSI_SESSION_DEBUG(is, "waking up iscsid(8)");
415255570Strasz	is->is_waiting_for_iscsid = true;
416255570Strasz	strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason));
417255570Strasz	is->is_timeout = 0;
418255570Strasz	ISCSI_SESSION_UNLOCK(is);
419255570Strasz	cv_signal(&is->is_softc->sc_cv);
420255570Strasz}
421255570Strasz
422255570Straszstatic void
423255570Strasziscsi_maintenance_thread_terminate(struct iscsi_session *is)
424255570Strasz{
425255570Strasz	struct iscsi_softc *sc;
426255570Strasz
427255570Strasz	sc = is->is_softc;
428255570Strasz	sx_xlock(&sc->sc_lock);
429325222Savg	TAILQ_REMOVE(&sc->sc_sessions, is, is_next);
430325222Savg	sx_xunlock(&sc->sc_lock);
431255570Strasz
432255570Strasz	icl_conn_close(is->is_conn);
433282955Strasz	callout_drain(&is->is_callout);
434255570Strasz
435255570Strasz	ISCSI_SESSION_LOCK(is);
436255570Strasz
437255570Strasz	KASSERT(is->is_terminating, ("is_terminating == false"));
438255570Strasz
439255570Strasz#ifdef ICL_KERNEL_PROXY
440255570Strasz	if (is->is_login_pdu != NULL) {
441255570Strasz		icl_pdu_free(is->is_login_pdu);
442255570Strasz		is->is_login_pdu = NULL;
443255570Strasz	}
444255570Strasz	cv_signal(&is->is_login_cv);
445255570Strasz#endif
446255570Strasz
447265523Strasz	iscsi_session_cleanup(is, true);
448255570Strasz
449255570Strasz	KASSERT(TAILQ_EMPTY(&is->is_outstanding),
450255570Strasz	    ("destroying session with active tasks"));
451265500Strasz	KASSERT(STAILQ_EMPTY(&is->is_postponed),
452255570Strasz	    ("destroying session with postponed PDUs"));
453255570Strasz
454255570Strasz	ISCSI_SESSION_UNLOCK(is);
455255570Strasz
456255570Strasz	icl_conn_free(is->is_conn);
457255570Strasz	mtx_destroy(&is->is_lock);
458255570Strasz	cv_destroy(&is->is_maintenance_cv);
459255570Strasz#ifdef ICL_KERNEL_PROXY
460255570Strasz	cv_destroy(&is->is_login_cv);
461255570Strasz#endif
462294708Ssmh
463255570Strasz	ISCSI_SESSION_DEBUG(is, "terminated");
464255570Strasz	free(is, M_ISCSI);
465255570Strasz
466255570Strasz	/*
467255570Strasz	 * The iscsi_unload() routine might be waiting.
468255570Strasz	 */
469255570Strasz	cv_signal(&sc->sc_cv);
470255570Strasz}
471255570Strasz
472255570Straszstatic void
473255570Strasziscsi_maintenance_thread(void *arg)
474255570Strasz{
475314581Smav	struct iscsi_session *is = arg;
476255570Strasz
477314581Smav	ISCSI_SESSION_LOCK(is);
478255570Strasz	for (;;) {
479255570Strasz		if (is->is_reconnecting == false &&
480255570Strasz		    is->is_terminating == false &&
481314581Smav		    (STAILQ_EMPTY(&is->is_postponed) ||
482314581Smav		     ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn)))
483255570Strasz			cv_wait(&is->is_maintenance_cv, &is->is_lock);
484255570Strasz
485294708Ssmh		/* Terminate supersedes reconnect. */
486255570Strasz		if (is->is_terminating) {
487255570Strasz			ISCSI_SESSION_UNLOCK(is);
488255570Strasz			iscsi_maintenance_thread_terminate(is);
489255570Strasz			kthread_exit();
490255570Strasz			return;
491255570Strasz		}
492255570Strasz
493294708Ssmh		if (is->is_reconnecting) {
494294708Ssmh			ISCSI_SESSION_UNLOCK(is);
495294708Ssmh			iscsi_maintenance_thread_reconnect(is);
496314581Smav			ISCSI_SESSION_LOCK(is);
497294708Ssmh			continue;
498294708Ssmh		}
499294708Ssmh
500255570Strasz		iscsi_session_send_postponed(is);
501255570Strasz	}
502314581Smav	ISCSI_SESSION_UNLOCK(is);
503255570Strasz}
504255570Strasz
505255570Straszstatic void
506255570Strasziscsi_session_reconnect(struct iscsi_session *is)
507255570Strasz{
508255570Strasz
509255570Strasz	/*
510255570Strasz	 * XXX: We can't use locking here, because
511255570Strasz	 * 	it's being called from various contexts.
512255570Strasz	 * 	Hope it doesn't break anything.
513255570Strasz	 */
514255570Strasz	if (is->is_reconnecting)
515255570Strasz		return;
516255570Strasz
517255570Strasz	is->is_reconnecting = true;
518255570Strasz	cv_signal(&is->is_maintenance_cv);
519255570Strasz}
520255570Strasz
521255570Straszstatic void
522255570Strasziscsi_session_terminate(struct iscsi_session *is)
523255570Strasz{
524282955Strasz
525255570Strasz	if (is->is_terminating)
526255570Strasz		return;
527255570Strasz
528255570Strasz	is->is_terminating = true;
529255570Strasz
530255570Strasz#if 0
531255570Strasz	iscsi_session_logout(is);
532255570Strasz#endif
533255570Strasz	cv_signal(&is->is_maintenance_cv);
534255570Strasz}
535255570Strasz
536255570Straszstatic void
537255570Strasziscsi_callout(void *context)
538255570Strasz{
539255570Strasz	struct icl_pdu *request;
540255570Strasz	struct iscsi_bhs_nop_out *bhsno;
541255570Strasz	struct iscsi_session *is;
542255570Strasz	bool reconnect_needed = false;
543255570Strasz
544255570Strasz	is = context;
545255570Strasz
546282955Strasz	ISCSI_SESSION_LOCK(is);
547282955Strasz	if (is->is_terminating) {
548282955Strasz		ISCSI_SESSION_UNLOCK(is);
549255570Strasz		return;
550282955Strasz	}
551255570Strasz
552255570Strasz	callout_schedule(&is->is_callout, 1 * hz);
553255570Strasz
554255570Strasz	is->is_timeout++;
555255570Strasz
556255570Strasz	if (is->is_waiting_for_iscsid) {
557273307Smav		if (iscsid_timeout > 0 && is->is_timeout > iscsid_timeout) {
558255570Strasz			ISCSI_SESSION_WARN(is, "timed out waiting for iscsid(8) "
559255570Strasz			    "for %d seconds; reconnecting",
560255570Strasz			    is->is_timeout);
561255570Strasz			reconnect_needed = true;
562255570Strasz		}
563255570Strasz		goto out;
564255570Strasz	}
565255570Strasz
566255570Strasz	if (is->is_login_phase) {
567273307Smav		if (login_timeout > 0 && is->is_timeout > login_timeout) {
568255570Strasz			ISCSI_SESSION_WARN(is, "login timed out after %d seconds; "
569255570Strasz			    "reconnecting", is->is_timeout);
570255570Strasz			reconnect_needed = true;
571255570Strasz		}
572255570Strasz		goto out;
573255570Strasz	}
574255570Strasz
575273307Smav	if (ping_timeout <= 0) {
576273307Smav		/*
577273307Smav		 * Pings are disabled.  Don't send NOP-Out in this case.
578273307Smav		 * Reset the timeout, to avoid triggering reconnection,
579273307Smav		 * should the user decide to reenable them.
580273307Smav		 */
581273307Smav		is->is_timeout = 0;
582273307Smav		goto out;
583273307Smav	}
584273307Smav
585255570Strasz	if (is->is_timeout >= ping_timeout) {
586255570Strasz		ISCSI_SESSION_WARN(is, "no ping reply (NOP-In) after %d seconds; "
587255570Strasz		    "reconnecting", ping_timeout);
588255570Strasz		reconnect_needed = true;
589255570Strasz		goto out;
590255570Strasz	}
591255570Strasz
592255570Strasz	ISCSI_SESSION_UNLOCK(is);
593255570Strasz
594255570Strasz	/*
595255570Strasz	 * If the ping was reset less than one second ago - which means
596255570Strasz	 * that we've received some PDU during the last second - assume
597255570Strasz	 * the traffic flows correctly and don't bother sending a NOP-Out.
598255570Strasz	 *
599255570Strasz	 * (It's 2 - one for one second, and one for incrementing is_timeout
600255570Strasz	 * earlier in this routine.)
601255570Strasz	 */
602255570Strasz	if (is->is_timeout < 2)
603255570Strasz		return;
604255570Strasz
605276618Smav	request = icl_pdu_new(is->is_conn, M_NOWAIT);
606255824Strasz	if (request == NULL) {
607255824Strasz		ISCSI_SESSION_WARN(is, "failed to allocate PDU");
608255824Strasz		return;
609255824Strasz	}
610255570Strasz	bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs;
611255570Strasz	bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT |
612255570Strasz	    ISCSI_BHS_OPCODE_IMMEDIATE;
613255570Strasz	bhsno->bhsno_flags = 0x80;
614255570Strasz	bhsno->bhsno_target_transfer_tag = 0xffffffff;
615255570Strasz	iscsi_pdu_queue(request);
616255570Strasz	return;
617255570Strasz
618255570Straszout:
619294708Ssmh	if (is->is_terminating) {
620294708Ssmh		ISCSI_SESSION_UNLOCK(is);
621294708Ssmh		return;
622294708Ssmh	}
623294708Ssmh
624255570Strasz	ISCSI_SESSION_UNLOCK(is);
625255570Strasz
626255570Strasz	if (reconnect_needed)
627255570Strasz		iscsi_session_reconnect(is);
628255570Strasz}
629255570Strasz
630255570Straszstatic void
631255570Strasziscsi_pdu_update_statsn(const struct icl_pdu *response)
632255570Strasz{
633255570Strasz	const struct iscsi_bhs_data_in *bhsdi;
634255570Strasz	struct iscsi_session *is;
635276613Smav	uint32_t expcmdsn, maxcmdsn, statsn;
636255570Strasz
637255570Strasz	is = PDU_SESSION(response);
638255570Strasz
639255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
640255570Strasz
641255570Strasz	/*
642255570Strasz	 * We're only using fields common for all the response
643255570Strasz	 * (target -> initiator) PDUs.
644255570Strasz	 */
645255570Strasz	bhsdi = (const struct iscsi_bhs_data_in *)response->ip_bhs;
646255570Strasz	/*
647255570Strasz	 * Ok, I lied.  In case of Data-In, "The fields StatSN, Status,
648255570Strasz	 * and Residual Count only have meaningful content if the S bit
649255570Strasz	 * is set to 1", so we also need to check the bit specific for
650255570Strasz	 * Data-In PDU.
651255570Strasz	 */
652255570Strasz	if (bhsdi->bhsdi_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN ||
653255570Strasz	    (bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0) {
654276613Smav		statsn = ntohl(bhsdi->bhsdi_statsn);
655276613Smav		if (statsn != is->is_statsn && statsn != (is->is_statsn + 1)) {
656276613Smav			/* XXX: This is normal situation for MCS */
657276613Smav			ISCSI_SESSION_WARN(is, "PDU 0x%x StatSN %u != "
658276613Smav			    "session ExpStatSN %u (or + 1); reconnecting",
659276613Smav			    bhsdi->bhsdi_opcode, statsn, is->is_statsn);
660276613Smav			iscsi_session_reconnect(is);
661255570Strasz		}
662276613Smav		if (ISCSI_SNGT(statsn, is->is_statsn))
663276613Smav			is->is_statsn = statsn;
664255570Strasz	}
665255570Strasz
666255570Strasz	expcmdsn = ntohl(bhsdi->bhsdi_expcmdsn);
667255570Strasz	maxcmdsn = ntohl(bhsdi->bhsdi_maxcmdsn);
668255570Strasz
669276613Smav	if (ISCSI_SNLT(maxcmdsn + 1, expcmdsn)) {
670276613Smav		ISCSI_SESSION_DEBUG(is,
671276613Smav		    "PDU MaxCmdSN %u + 1 < PDU ExpCmdSN %u; ignoring",
672255570Strasz		    maxcmdsn, expcmdsn);
673255570Strasz	} else {
674276613Smav		if (ISCSI_SNGT(maxcmdsn, is->is_maxcmdsn)) {
675255570Strasz			is->is_maxcmdsn = maxcmdsn;
676255570Strasz
677255570Strasz			/*
678255570Strasz			 * Command window increased; kick the maintanance thread
679255570Strasz			 * to send out postponed commands.
680255570Strasz			 */
681265500Strasz			if (!STAILQ_EMPTY(&is->is_postponed))
682255570Strasz				cv_signal(&is->is_maintenance_cv);
683276613Smav		} else if (ISCSI_SNLT(maxcmdsn, is->is_maxcmdsn)) {
684276613Smav			/* XXX: This is normal situation for MCS */
685276613Smav			ISCSI_SESSION_DEBUG(is,
686276613Smav			    "PDU MaxCmdSN %u < session MaxCmdSN %u; ignoring",
687255570Strasz			    maxcmdsn, is->is_maxcmdsn);
688255570Strasz		}
689255570Strasz
690276613Smav		if (ISCSI_SNGT(expcmdsn, is->is_expcmdsn)) {
691255570Strasz			is->is_expcmdsn = expcmdsn;
692276613Smav		} else if (ISCSI_SNLT(expcmdsn, is->is_expcmdsn)) {
693276613Smav			/* XXX: This is normal situation for MCS */
694276613Smav			ISCSI_SESSION_DEBUG(is,
695276613Smav			    "PDU ExpCmdSN %u < session ExpCmdSN %u; ignoring",
696255570Strasz			    expcmdsn, is->is_expcmdsn);
697255570Strasz		}
698255570Strasz	}
699255570Strasz
700255570Strasz	/*
701255570Strasz	 * Every incoming PDU - not just NOP-In - resets the ping timer.
702255570Strasz	 * The purpose of the timeout is to reset the connection when it stalls;
703255570Strasz	 * we don't want this to happen when NOP-In or NOP-Out ends up delayed
704255570Strasz	 * in some queue.
705255570Strasz	 */
706255570Strasz	is->is_timeout = 0;
707255570Strasz}
708255570Strasz
709255570Straszstatic void
710255570Strasziscsi_receive_callback(struct icl_pdu *response)
711255570Strasz{
712255570Strasz	struct iscsi_session *is;
713255570Strasz
714255570Strasz	is = PDU_SESSION(response);
715255570Strasz
716255570Strasz	ISCSI_SESSION_LOCK(is);
717255570Strasz
718255570Strasz#ifdef ICL_KERNEL_PROXY
719255570Strasz	if (is->is_login_phase) {
720255570Strasz		if (is->is_login_pdu == NULL)
721255570Strasz			is->is_login_pdu = response;
722255570Strasz		else
723255570Strasz			icl_pdu_free(response);
724255570Strasz		ISCSI_SESSION_UNLOCK(is);
725255570Strasz		cv_signal(&is->is_login_cv);
726255570Strasz		return;
727255570Strasz	}
728255570Strasz#endif
729255570Strasz
730255570Strasz	iscsi_pdu_update_statsn(response);
731255570Strasz
732255570Strasz	/*
733255570Strasz	 * The handling routine is responsible for freeing the PDU
734255570Strasz	 * when it's no longer needed.
735255570Strasz	 */
736255570Strasz	switch (response->ip_bhs->bhs_opcode) {
737255570Strasz	case ISCSI_BHS_OPCODE_NOP_IN:
738255570Strasz		iscsi_pdu_handle_nop_in(response);
739275559Smav		ISCSI_SESSION_UNLOCK(is);
740255570Strasz		break;
741255570Strasz	case ISCSI_BHS_OPCODE_SCSI_RESPONSE:
742255570Strasz		iscsi_pdu_handle_scsi_response(response);
743275559Smav		/* Session lock dropped inside. */
744275559Smav		ISCSI_SESSION_LOCK_ASSERT_NOT(is);
745255570Strasz		break;
746268705Smav	case ISCSI_BHS_OPCODE_TASK_RESPONSE:
747268705Smav		iscsi_pdu_handle_task_response(response);
748275559Smav		ISCSI_SESSION_UNLOCK(is);
749268705Smav		break;
750255570Strasz	case ISCSI_BHS_OPCODE_SCSI_DATA_IN:
751255570Strasz		iscsi_pdu_handle_data_in(response);
752275559Smav		/* Session lock dropped inside. */
753275559Smav		ISCSI_SESSION_LOCK_ASSERT_NOT(is);
754255570Strasz		break;
755255570Strasz	case ISCSI_BHS_OPCODE_LOGOUT_RESPONSE:
756255570Strasz		iscsi_pdu_handle_logout_response(response);
757275559Smav		ISCSI_SESSION_UNLOCK(is);
758255570Strasz		break;
759255570Strasz	case ISCSI_BHS_OPCODE_R2T:
760255570Strasz		iscsi_pdu_handle_r2t(response);
761275559Smav		ISCSI_SESSION_UNLOCK(is);
762255570Strasz		break;
763255570Strasz	case ISCSI_BHS_OPCODE_ASYNC_MESSAGE:
764255570Strasz		iscsi_pdu_handle_async_message(response);
765275559Smav		ISCSI_SESSION_UNLOCK(is);
766255570Strasz		break;
767255570Strasz	case ISCSI_BHS_OPCODE_REJECT:
768255570Strasz		iscsi_pdu_handle_reject(response);
769275559Smav		ISCSI_SESSION_UNLOCK(is);
770255570Strasz		break;
771255570Strasz	default:
772255570Strasz		ISCSI_SESSION_WARN(is, "received PDU with unsupported "
773255570Strasz		    "opcode 0x%x; reconnecting",
774255570Strasz		    response->ip_bhs->bhs_opcode);
775255570Strasz		iscsi_session_reconnect(is);
776275559Smav		ISCSI_SESSION_UNLOCK(is);
777255570Strasz		icl_pdu_free(response);
778255570Strasz	}
779255570Strasz}
780255570Strasz
781255570Straszstatic void
782255570Strasziscsi_error_callback(struct icl_conn *ic)
783255570Strasz{
784255570Strasz	struct iscsi_session *is;
785255570Strasz
786255570Strasz	is = CONN_SESSION(ic);
787255570Strasz
788255570Strasz	ISCSI_SESSION_WARN(is, "connection error; reconnecting");
789255570Strasz	iscsi_session_reconnect(is);
790255570Strasz}
791255570Strasz
792255570Straszstatic void
793255570Strasziscsi_pdu_handle_nop_in(struct icl_pdu *response)
794255570Strasz{
795256187Strasz	struct iscsi_session *is;
796255570Strasz	struct iscsi_bhs_nop_out *bhsno;
797255570Strasz	struct iscsi_bhs_nop_in *bhsni;
798255570Strasz	struct icl_pdu *request;
799256187Strasz	void *data = NULL;
800256187Strasz	size_t datasize;
801256187Strasz	int error;
802255570Strasz
803256187Strasz	is = PDU_SESSION(response);
804255570Strasz	bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs;
805255570Strasz
806255570Strasz	if (bhsni->bhsni_target_transfer_tag == 0xffffffff) {
807255570Strasz		/*
808255570Strasz		 * Nothing to do; iscsi_pdu_update_statsn() already
809255570Strasz		 * zeroed the timeout.
810255570Strasz		 */
811255570Strasz		icl_pdu_free(response);
812255570Strasz		return;
813255570Strasz	}
814255570Strasz
815256187Strasz	datasize = icl_pdu_data_segment_length(response);
816256187Strasz	if (datasize > 0) {
817256187Strasz		data = malloc(datasize, M_ISCSI, M_NOWAIT | M_ZERO);
818256187Strasz		if (data == NULL) {
819256187Strasz			ISCSI_SESSION_WARN(is, "failed to allocate memory; "
820256187Strasz			    "reconnecting");
821256187Strasz			icl_pdu_free(response);
822256187Strasz			iscsi_session_reconnect(is);
823256187Strasz			return;
824256187Strasz		}
825256187Strasz		icl_pdu_get_data(response, 0, data, datasize);
826256187Strasz	}
827256187Strasz
828276618Smav	request = icl_pdu_new(response->ip_conn, M_NOWAIT);
829255570Strasz	if (request == NULL) {
830256187Strasz		ISCSI_SESSION_WARN(is, "failed to allocate memory; "
831256187Strasz		    "reconnecting");
832256187Strasz		free(data, M_ISCSI);
833255570Strasz		icl_pdu_free(response);
834256187Strasz		iscsi_session_reconnect(is);
835255570Strasz		return;
836255570Strasz	}
837255570Strasz	bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs;
838255570Strasz	bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT |
839255570Strasz	    ISCSI_BHS_OPCODE_IMMEDIATE;
840255570Strasz	bhsno->bhsno_flags = 0x80;
841256187Strasz	bhsno->bhsno_initiator_task_tag = 0xffffffff;
842255570Strasz	bhsno->bhsno_target_transfer_tag = bhsni->bhsni_target_transfer_tag;
843256187Strasz	if (datasize > 0) {
844256187Strasz		error = icl_pdu_append_data(request, data, datasize, M_NOWAIT);
845256187Strasz		if (error != 0) {
846256187Strasz			ISCSI_SESSION_WARN(is, "failed to allocate memory; "
847256187Strasz			    "reconnecting");
848256187Strasz			free(data, M_ISCSI);
849256187Strasz			icl_pdu_free(request);
850256187Strasz			icl_pdu_free(response);
851256187Strasz			iscsi_session_reconnect(is);
852256187Strasz			return;
853256187Strasz		}
854256187Strasz		free(data, M_ISCSI);
855256187Strasz	}
856255570Strasz
857255570Strasz	icl_pdu_free(response);
858255570Strasz	iscsi_pdu_queue_locked(request);
859255570Strasz}
860255570Strasz
861255570Straszstatic void
862255570Strasziscsi_pdu_handle_scsi_response(struct icl_pdu *response)
863255570Strasz{
864255570Strasz	struct iscsi_bhs_scsi_response *bhssr;
865255570Strasz	struct iscsi_outstanding *io;
866255570Strasz	struct iscsi_session *is;
867275559Smav	union ccb *ccb;
868255570Strasz	struct ccb_scsiio *csio;
869275559Smav	size_t data_segment_len, received;
870255570Strasz	uint16_t sense_len;
871255570Strasz
872255570Strasz	is = PDU_SESSION(response);
873255570Strasz
874255570Strasz	bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs;
875255570Strasz	io = iscsi_outstanding_find(is, bhssr->bhssr_initiator_task_tag);
876268705Smav	if (io == NULL || io->io_ccb == NULL) {
877255570Strasz		ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhssr->bhssr_initiator_task_tag);
878255570Strasz		icl_pdu_free(response);
879255570Strasz		iscsi_session_reconnect(is);
880275559Smav		ISCSI_SESSION_UNLOCK(is);
881255570Strasz		return;
882255570Strasz	}
883255570Strasz
884275559Smav	ccb = io->io_ccb;
885275559Smav	received = io->io_received;
886275559Smav	iscsi_outstanding_remove(is, io);
887275559Smav	ISCSI_SESSION_UNLOCK(is);
888275559Smav
889255570Strasz	if (bhssr->bhssr_response != BHSSR_RESPONSE_COMMAND_COMPLETED) {
890255570Strasz		ISCSI_SESSION_WARN(is, "service response 0x%x", bhssr->bhssr_response);
891275559Smav 		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
892275559Smav 			xpt_freeze_devq(ccb->ccb_h.path, 1);
893255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
894255570Strasz		}
895275559Smav 		ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
896255570Strasz	} else if (bhssr->bhssr_status == 0) {
897275559Smav		ccb->ccb_h.status = CAM_REQ_CMP;
898255570Strasz	} else {
899275559Smav 		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
900275559Smav 			xpt_freeze_devq(ccb->ccb_h.path, 1);
901255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
902255570Strasz		}
903275559Smav 		ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN;
904275559Smav		ccb->csio.scsi_status = bhssr->bhssr_status;
905255570Strasz	}
906255570Strasz
907275559Smav	csio = &ccb->csio;
908255570Strasz	data_segment_len = icl_pdu_data_segment_length(response);
909255570Strasz	if (data_segment_len > 0) {
910255570Strasz		if (data_segment_len < sizeof(sense_len)) {
911255570Strasz			ISCSI_SESSION_WARN(is, "truncated data segment (%zd bytes)",
912255570Strasz			    data_segment_len);
913275559Smav			if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
914275559Smav				xpt_freeze_devq(ccb->ccb_h.path, 1);
915255570Strasz				ISCSI_SESSION_DEBUG(is, "freezing devq");
916255570Strasz			}
917275559Smav			ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
918255570Strasz			goto out;
919255570Strasz		}
920255570Strasz		icl_pdu_get_data(response, 0, &sense_len, sizeof(sense_len));
921255570Strasz		sense_len = ntohs(sense_len);
922255570Strasz#if 0
923255570Strasz		ISCSI_SESSION_DEBUG(is, "sense_len %d, data len %zd",
924255570Strasz		    sense_len, data_segment_len);
925255570Strasz#endif
926255570Strasz		if (sizeof(sense_len) + sense_len > data_segment_len) {
927255570Strasz			ISCSI_SESSION_WARN(is, "truncated data segment "
928255570Strasz			    "(%zd bytes, should be %zd)",
929255570Strasz			    data_segment_len, sizeof(sense_len) + sense_len);
930275559Smav			if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
931275559Smav				xpt_freeze_devq(ccb->ccb_h.path, 1);
932255570Strasz				ISCSI_SESSION_DEBUG(is, "freezing devq");
933255570Strasz			}
934275559Smav			ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
935255570Strasz			goto out;
936255570Strasz		} else if (sizeof(sense_len) + sense_len < data_segment_len)
937255570Strasz			ISCSI_SESSION_WARN(is, "oversize data segment "
938255570Strasz			    "(%zd bytes, should be %zd)",
939255570Strasz			    data_segment_len, sizeof(sense_len) + sense_len);
940255570Strasz		if (sense_len > csio->sense_len) {
941255570Strasz			ISCSI_SESSION_DEBUG(is, "truncating sense from %d to %d",
942255570Strasz			    sense_len, csio->sense_len);
943255570Strasz			sense_len = csio->sense_len;
944255570Strasz		}
945255570Strasz		icl_pdu_get_data(response, sizeof(sense_len), &csio->sense_data, sense_len);
946255570Strasz		csio->sense_resid = csio->sense_len - sense_len;
947275559Smav		ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
948255570Strasz	}
949255570Strasz
950255570Straszout:
951255570Strasz	if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_UNDERFLOW)
952255570Strasz		csio->resid = ntohl(bhssr->bhssr_residual_count);
953255570Strasz
954255570Strasz	if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
955275559Smav		KASSERT(received <= csio->dxfer_len,
956275559Smav		    ("received > csio->dxfer_len"));
957275559Smav		if (received < csio->dxfer_len) {
958275559Smav			if (csio->resid != csio->dxfer_len - received) {
959255570Strasz				ISCSI_SESSION_WARN(is, "underflow mismatch: "
960255570Strasz				    "target indicates %d, we calculated %zd",
961275559Smav				    csio->resid, csio->dxfer_len - received);
962255570Strasz			}
963275559Smav			csio->resid = csio->dxfer_len - received;
964255570Strasz		}
965255570Strasz	}
966255570Strasz
967275559Smav	xpt_done(ccb);
968255570Strasz	icl_pdu_free(response);
969255570Strasz}
970255570Strasz
971255570Straszstatic void
972268705Smaviscsi_pdu_handle_task_response(struct icl_pdu *response)
973268705Smav{
974268705Smav	struct iscsi_bhs_task_management_response *bhstmr;
975268705Smav	struct iscsi_outstanding *io, *aio;
976268705Smav	struct iscsi_session *is;
977268705Smav
978268705Smav	is = PDU_SESSION(response);
979268705Smav
980268705Smav	bhstmr = (struct iscsi_bhs_task_management_response *)response->ip_bhs;
981268705Smav	io = iscsi_outstanding_find(is, bhstmr->bhstmr_initiator_task_tag);
982268705Smav	if (io == NULL || io->io_ccb != NULL) {
983268705Smav		ISCSI_SESSION_WARN(is, "bad itt 0x%x",
984268705Smav		    bhstmr->bhstmr_initiator_task_tag);
985268705Smav		icl_pdu_free(response);
986268705Smav		iscsi_session_reconnect(is);
987268705Smav		return;
988268705Smav	}
989268705Smav
990268705Smav	if (bhstmr->bhstmr_response != BHSTMR_RESPONSE_FUNCTION_COMPLETE) {
991268705Smav		ISCSI_SESSION_WARN(is, "task response 0x%x",
992268705Smav		    bhstmr->bhstmr_response);
993268705Smav	} else {
994268705Smav		aio = iscsi_outstanding_find(is, io->io_datasn);
995268705Smav		if (aio != NULL && aio->io_ccb != NULL)
996325224Savg			iscsi_session_terminate_task(is, aio, CAM_REQ_ABORTED);
997268705Smav	}
998268705Smav
999268705Smav	iscsi_outstanding_remove(is, io);
1000268705Smav	icl_pdu_free(response);
1001268705Smav}
1002268705Smav
1003268705Smavstatic void
1004255570Strasziscsi_pdu_handle_data_in(struct icl_pdu *response)
1005255570Strasz{
1006255570Strasz	struct iscsi_bhs_data_in *bhsdi;
1007255570Strasz	struct iscsi_outstanding *io;
1008255570Strasz	struct iscsi_session *is;
1009275559Smav	union ccb *ccb;
1010255570Strasz	struct ccb_scsiio *csio;
1011275559Smav	size_t data_segment_len, received, oreceived;
1012255570Strasz
1013255570Strasz	is = PDU_SESSION(response);
1014255570Strasz	bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs;
1015255570Strasz	io = iscsi_outstanding_find(is, bhsdi->bhsdi_initiator_task_tag);
1016268705Smav	if (io == NULL || io->io_ccb == NULL) {
1017255570Strasz		ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhsdi->bhsdi_initiator_task_tag);
1018255570Strasz		icl_pdu_free(response);
1019255570Strasz		iscsi_session_reconnect(is);
1020275559Smav		ISCSI_SESSION_UNLOCK(is);
1021255570Strasz		return;
1022255570Strasz	}
1023255570Strasz
1024255570Strasz	data_segment_len = icl_pdu_data_segment_length(response);
1025255570Strasz	if (data_segment_len == 0) {
1026255570Strasz		/*
1027255570Strasz		 * "The sending of 0 length data segments should be avoided,
1028255570Strasz		 * but initiators and targets MUST be able to properly receive
1029255570Strasz		 * 0 length data segments."
1030255570Strasz		 */
1031275559Smav		ISCSI_SESSION_UNLOCK(is);
1032255570Strasz		icl_pdu_free(response);
1033255570Strasz		return;
1034255570Strasz	}
1035255570Strasz
1036255570Strasz	/*
1037255570Strasz	 * We need to track this for security reasons - without it, malicious target
1038255570Strasz	 * could respond to SCSI READ without sending Data-In PDUs, which would result
1039255570Strasz	 * in read operation on the initiator side returning random kernel data.
1040255570Strasz	 */
1041255570Strasz	if (ntohl(bhsdi->bhsdi_buffer_offset) != io->io_received) {
1042255570Strasz		ISCSI_SESSION_WARN(is, "data out of order; expected offset %zd, got %zd",
1043255570Strasz		    io->io_received, (size_t)ntohl(bhsdi->bhsdi_buffer_offset));
1044255570Strasz		icl_pdu_free(response);
1045255570Strasz		iscsi_session_reconnect(is);
1046275559Smav		ISCSI_SESSION_UNLOCK(is);
1047255570Strasz		return;
1048255570Strasz	}
1049255570Strasz
1050275559Smav	ccb = io->io_ccb;
1051275559Smav	csio = &ccb->csio;
1052255570Strasz
1053256229Strasz	if (io->io_received + data_segment_len > csio->dxfer_len) {
1054255570Strasz		ISCSI_SESSION_WARN(is, "oversize data segment (%zd bytes "
1055256229Strasz		    "at offset %zd, buffer is %d)",
1056256229Strasz		    data_segment_len, io->io_received, csio->dxfer_len);
1057255570Strasz		icl_pdu_free(response);
1058255570Strasz		iscsi_session_reconnect(is);
1059275559Smav		ISCSI_SESSION_UNLOCK(is);
1060255570Strasz		return;
1061255570Strasz	}
1062255570Strasz
1063275559Smav	oreceived = io->io_received;
1064255570Strasz	io->io_received += data_segment_len;
1065275559Smav	received = io->io_received;
1066275559Smav	if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0)
1067275559Smav		iscsi_outstanding_remove(is, io);
1068275559Smav	ISCSI_SESSION_UNLOCK(is);
1069255570Strasz
1070275559Smav	icl_pdu_get_data(response, 0, csio->data_ptr + oreceived, data_segment_len);
1071275559Smav
1072255570Strasz	/*
1073255570Strasz	 * XXX: Check DataSN.
1074255570Strasz	 * XXX: Check F.
1075255570Strasz	 */
1076256238Strasz	if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) == 0) {
1077256238Strasz		/*
1078256238Strasz		 * Nothing more to do.
1079256238Strasz		 */
1080256238Strasz		icl_pdu_free(response);
1081256238Strasz		return;
1082256238Strasz	}
1083256238Strasz
1084256238Strasz	//ISCSI_SESSION_DEBUG(is, "got S flag; status 0x%x", bhsdi->bhsdi_status);
1085256238Strasz	if (bhsdi->bhsdi_status == 0) {
1086275559Smav		ccb->ccb_h.status = CAM_REQ_CMP;
1087256238Strasz	} else {
1088275559Smav		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
1089275559Smav			xpt_freeze_devq(ccb->ccb_h.path, 1);
1090256238Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
1091256238Strasz		}
1092275559Smav		ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN;
1093256238Strasz		csio->scsi_status = bhsdi->bhsdi_status;
1094256238Strasz	}
1095256238Strasz
1096256238Strasz	if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
1097275559Smav		KASSERT(received <= csio->dxfer_len,
1098275559Smav		    ("received > csio->dxfer_len"));
1099275559Smav		if (received < csio->dxfer_len) {
1100256238Strasz			csio->resid = ntohl(bhsdi->bhsdi_residual_count);
1101275559Smav			if (csio->resid != csio->dxfer_len - received) {
1102256238Strasz				ISCSI_SESSION_WARN(is, "underflow mismatch: "
1103256238Strasz				    "target indicates %d, we calculated %zd",
1104275559Smav				    csio->resid, csio->dxfer_len - received);
1105255570Strasz			}
1106275559Smav			csio->resid = csio->dxfer_len - received;
1107255570Strasz		}
1108255570Strasz	}
1109255570Strasz
1110275559Smav	xpt_done(ccb);
1111255570Strasz	icl_pdu_free(response);
1112255570Strasz}
1113255570Strasz
1114255570Straszstatic void
1115255570Strasziscsi_pdu_handle_logout_response(struct icl_pdu *response)
1116255570Strasz{
1117255570Strasz
1118255570Strasz	ISCSI_SESSION_DEBUG(PDU_SESSION(response), "logout response");
1119255570Strasz	icl_pdu_free(response);
1120255570Strasz}
1121255570Strasz
1122255570Straszstatic void
1123255570Strasziscsi_pdu_handle_r2t(struct icl_pdu *response)
1124255570Strasz{
1125255570Strasz	struct icl_pdu *request;
1126255570Strasz	struct iscsi_session *is;
1127255570Strasz	struct iscsi_bhs_r2t *bhsr2t;
1128255570Strasz	struct iscsi_bhs_data_out *bhsdo;
1129255570Strasz	struct iscsi_outstanding *io;
1130255570Strasz	struct ccb_scsiio *csio;
1131255570Strasz	size_t off, len, total_len;
1132255570Strasz	int error;
1133255570Strasz
1134255570Strasz	is = PDU_SESSION(response);
1135255570Strasz
1136255570Strasz	bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs;
1137255570Strasz	io = iscsi_outstanding_find(is, bhsr2t->bhsr2t_initiator_task_tag);
1138268705Smav	if (io == NULL || io->io_ccb == NULL) {
1139255570Strasz		ISCSI_SESSION_WARN(is, "bad itt 0x%x; reconnecting",
1140255570Strasz		    bhsr2t->bhsr2t_initiator_task_tag);
1141255570Strasz		icl_pdu_free(response);
1142255570Strasz		iscsi_session_reconnect(is);
1143255570Strasz		return;
1144255570Strasz	}
1145255570Strasz
1146255570Strasz	csio = &io->io_ccb->csio;
1147255570Strasz
1148255570Strasz	if ((csio->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_OUT) {
1149255570Strasz		ISCSI_SESSION_WARN(is, "received R2T for read command; reconnecting");
1150255570Strasz		icl_pdu_free(response);
1151255570Strasz		iscsi_session_reconnect(is);
1152255570Strasz		return;
1153255570Strasz	}
1154255570Strasz
1155255570Strasz	/*
1156255570Strasz	 * XXX: Verify R2TSN.
1157255570Strasz	 */
1158255570Strasz
1159255570Strasz	io->io_datasn = 0;
1160256229Strasz
1161255570Strasz	off = ntohl(bhsr2t->bhsr2t_buffer_offset);
1162256229Strasz	if (off > csio->dxfer_len) {
1163256229Strasz		ISCSI_SESSION_WARN(is, "target requested invalid offset "
1164256229Strasz		    "%zd, buffer is is %d; reconnecting", off, csio->dxfer_len);
1165256229Strasz		icl_pdu_free(response);
1166256229Strasz		iscsi_session_reconnect(is);
1167256229Strasz		return;
1168256229Strasz	}
1169256229Strasz
1170255570Strasz	total_len = ntohl(bhsr2t->bhsr2t_desired_data_transfer_length);
1171256229Strasz	if (total_len == 0 || total_len > csio->dxfer_len) {
1172256229Strasz		ISCSI_SESSION_WARN(is, "target requested invalid length "
1173256229Strasz		    "%zd, buffer is %d; reconnecting", total_len, csio->dxfer_len);
1174256229Strasz		icl_pdu_free(response);
1175256229Strasz		iscsi_session_reconnect(is);
1176256229Strasz		return;
1177256229Strasz	}
1178255570Strasz
1179255570Strasz	//ISCSI_SESSION_DEBUG(is, "r2t; off %zd, len %zd", off, total_len);
1180255570Strasz
1181255570Strasz	for (;;) {
1182255570Strasz		len = total_len;
1183255570Strasz
1184255570Strasz		if (len > is->is_max_data_segment_length)
1185255570Strasz			len = is->is_max_data_segment_length;
1186255570Strasz
1187255570Strasz		if (off + len > csio->dxfer_len) {
1188256229Strasz			ISCSI_SESSION_WARN(is, "target requested invalid "
1189256229Strasz			    "length/offset %zd, buffer is %d; reconnecting",
1190255570Strasz			    off + len, csio->dxfer_len);
1191255570Strasz			icl_pdu_free(response);
1192255570Strasz			iscsi_session_reconnect(is);
1193255570Strasz			return;
1194255570Strasz		}
1195255570Strasz
1196276618Smav		request = icl_pdu_new(response->ip_conn, M_NOWAIT);
1197255570Strasz		if (request == NULL) {
1198255570Strasz			icl_pdu_free(response);
1199255570Strasz			iscsi_session_reconnect(is);
1200255570Strasz			return;
1201255570Strasz		}
1202255570Strasz
1203255570Strasz		bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs;
1204255570Strasz		bhsdo->bhsdo_opcode = ISCSI_BHS_OPCODE_SCSI_DATA_OUT;
1205255570Strasz		bhsdo->bhsdo_lun = bhsr2t->bhsr2t_lun;
1206255570Strasz		bhsdo->bhsdo_initiator_task_tag =
1207255570Strasz		    bhsr2t->bhsr2t_initiator_task_tag;
1208255570Strasz		bhsdo->bhsdo_target_transfer_tag =
1209255570Strasz		    bhsr2t->bhsr2t_target_transfer_tag;
1210255570Strasz		bhsdo->bhsdo_datasn = htonl(io->io_datasn++);
1211255570Strasz		bhsdo->bhsdo_buffer_offset = htonl(off);
1212256229Strasz		error = icl_pdu_append_data(request, csio->data_ptr + off, len,
1213256229Strasz		    M_NOWAIT);
1214255570Strasz		if (error != 0) {
1215256229Strasz			ISCSI_SESSION_WARN(is, "failed to allocate memory; "
1216256229Strasz			    "reconnecting");
1217255570Strasz			icl_pdu_free(request);
1218255570Strasz			icl_pdu_free(response);
1219255570Strasz			iscsi_session_reconnect(is);
1220255570Strasz			return;
1221255570Strasz		}
1222255570Strasz
1223255570Strasz		off += len;
1224255570Strasz		total_len -= len;
1225255570Strasz
1226255570Strasz		if (total_len == 0) {
1227255570Strasz			bhsdo->bhsdo_flags |= BHSDO_FLAGS_F;
1228255570Strasz			//ISCSI_SESSION_DEBUG(is, "setting F, off %zd", off);
1229255570Strasz		} else {
1230255570Strasz			//ISCSI_SESSION_DEBUG(is, "not finished, off %zd", off);
1231255570Strasz		}
1232255570Strasz
1233255570Strasz		iscsi_pdu_queue_locked(request);
1234255570Strasz
1235255570Strasz		if (total_len == 0)
1236255570Strasz			break;
1237255570Strasz	}
1238255570Strasz
1239255570Strasz	icl_pdu_free(response);
1240255570Strasz}
1241255570Strasz
1242255570Straszstatic void
1243255570Strasziscsi_pdu_handle_async_message(struct icl_pdu *response)
1244255570Strasz{
1245255570Strasz	struct iscsi_bhs_asynchronous_message *bhsam;
1246255570Strasz	struct iscsi_session *is;
1247255570Strasz
1248255570Strasz	is = PDU_SESSION(response);
1249255570Strasz	bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs;
1250255570Strasz	switch (bhsam->bhsam_async_event) {
1251255570Strasz	case BHSAM_EVENT_TARGET_REQUESTS_LOGOUT:
1252255570Strasz		ISCSI_SESSION_WARN(is, "target requests logout; removing session");
1253255570Strasz		iscsi_session_logout(is);
1254255570Strasz		iscsi_session_terminate(is);
1255255570Strasz		break;
1256255570Strasz	case BHSAM_EVENT_TARGET_TERMINATES_CONNECTION:
1257255570Strasz		ISCSI_SESSION_WARN(is, "target indicates it will drop drop the connection");
1258255570Strasz		break;
1259255570Strasz	case BHSAM_EVENT_TARGET_TERMINATES_SESSION:
1260255570Strasz		ISCSI_SESSION_WARN(is, "target indicates it will drop drop the session");
1261255570Strasz		break;
1262255570Strasz	default:
1263255570Strasz		/*
1264255570Strasz		 * XXX: Technically, we're obligated to also handle
1265255570Strasz		 * 	parameter renegotiation.
1266255570Strasz		 */
1267255570Strasz		ISCSI_SESSION_WARN(is, "ignoring AsyncEvent %d", bhsam->bhsam_async_event);
1268255570Strasz		break;
1269255570Strasz	}
1270255570Strasz
1271255570Strasz	icl_pdu_free(response);
1272255570Strasz}
1273255570Strasz
1274255570Straszstatic void
1275255570Strasziscsi_pdu_handle_reject(struct icl_pdu *response)
1276255570Strasz{
1277255570Strasz	struct iscsi_bhs_reject *bhsr;
1278255570Strasz	struct iscsi_session *is;
1279255570Strasz
1280255570Strasz	is = PDU_SESSION(response);
1281255570Strasz	bhsr = (struct iscsi_bhs_reject *)response->ip_bhs;
1282255570Strasz	ISCSI_SESSION_WARN(is, "received Reject PDU, reason 0x%x; protocol error?",
1283255570Strasz	    bhsr->bhsr_reason);
1284255570Strasz
1285255570Strasz	icl_pdu_free(response);
1286255570Strasz}
1287255570Strasz
1288255570Straszstatic int
1289255570Strasziscsi_ioctl_daemon_wait(struct iscsi_softc *sc,
1290255570Strasz    struct iscsi_daemon_request *request)
1291255570Strasz{
1292255570Strasz	struct iscsi_session *is;
1293255570Strasz	int error;
1294255570Strasz
1295255570Strasz	sx_slock(&sc->sc_lock);
1296255570Strasz	for (;;) {
1297255570Strasz		TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1298265521Strasz			ISCSI_SESSION_LOCK(is);
1299255570Strasz			if (is->is_waiting_for_iscsid)
1300255570Strasz				break;
1301265521Strasz			ISCSI_SESSION_UNLOCK(is);
1302255570Strasz		}
1303255570Strasz
1304255570Strasz		if (is == NULL) {
1305255570Strasz			/*
1306255570Strasz			 * No session requires attention from iscsid(8); wait.
1307255570Strasz			 */
1308255570Strasz			error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock);
1309255570Strasz			if (error != 0) {
1310255570Strasz				sx_sunlock(&sc->sc_lock);
1311255570Strasz				return (error);
1312255570Strasz			}
1313255570Strasz			continue;
1314255570Strasz		}
1315255570Strasz
1316255570Strasz		is->is_waiting_for_iscsid = false;
1317255570Strasz		is->is_login_phase = true;
1318255570Strasz		is->is_reason[0] = '\0';
1319255570Strasz		ISCSI_SESSION_UNLOCK(is);
1320255570Strasz
1321255570Strasz		request->idr_session_id = is->is_id;
1322268703Smav		memcpy(&request->idr_isid, &is->is_isid,
1323268703Smav		    sizeof(request->idr_isid));
1324268703Smav		request->idr_tsih = 0;	/* New or reinstated session. */
1325255570Strasz		memcpy(&request->idr_conf, &is->is_conf,
1326255570Strasz		    sizeof(request->idr_conf));
1327255570Strasz
1328255570Strasz		sx_sunlock(&sc->sc_lock);
1329255570Strasz		return (0);
1330255570Strasz	}
1331255570Strasz}
1332255570Strasz
1333255570Straszstatic int
1334255570Strasziscsi_ioctl_daemon_handoff(struct iscsi_softc *sc,
1335255570Strasz    struct iscsi_daemon_handoff *handoff)
1336255570Strasz{
1337255570Strasz	struct iscsi_session *is;
1338255570Strasz	int error;
1339255570Strasz
1340255570Strasz	sx_slock(&sc->sc_lock);
1341255570Strasz
1342255570Strasz	/*
1343255570Strasz	 * Find the session to hand off socket to.
1344255570Strasz	 */
1345255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1346255570Strasz		if (is->is_id == handoff->idh_session_id)
1347255570Strasz			break;
1348255570Strasz	}
1349255570Strasz	if (is == NULL) {
1350255570Strasz		sx_sunlock(&sc->sc_lock);
1351255570Strasz		return (ESRCH);
1352255570Strasz	}
1353255570Strasz	ISCSI_SESSION_LOCK(is);
1354255570Strasz	if (is->is_conf.isc_discovery || is->is_terminating) {
1355255570Strasz		ISCSI_SESSION_UNLOCK(is);
1356255570Strasz		sx_sunlock(&sc->sc_lock);
1357255570Strasz		return (EINVAL);
1358255570Strasz	}
1359259306Strasz	if (is->is_connected) {
1360259306Strasz		/*
1361259306Strasz		 * This might have happened because another iscsid(8)
1362259306Strasz		 * instance handed off the connection in the meantime.
1363259306Strasz		 * Just return.
1364259306Strasz		 */
1365259306Strasz		ISCSI_SESSION_WARN(is, "handoff on already connected "
1366259306Strasz		    "session");
1367259306Strasz		ISCSI_SESSION_UNLOCK(is);
1368259306Strasz		sx_sunlock(&sc->sc_lock);
1369259306Strasz		return (EBUSY);
1370259306Strasz	}
1371255570Strasz
1372255570Strasz	strlcpy(is->is_target_alias, handoff->idh_target_alias,
1373255570Strasz	    sizeof(is->is_target_alias));
1374268703Smav	is->is_tsih = handoff->idh_tsih;
1375255570Strasz	is->is_statsn = handoff->idh_statsn;
1376255570Strasz	is->is_initial_r2t = handoff->idh_initial_r2t;
1377255570Strasz	is->is_immediate_data = handoff->idh_immediate_data;
1378255570Strasz	is->is_max_data_segment_length = handoff->idh_max_data_segment_length;
1379255570Strasz	is->is_max_burst_length = handoff->idh_max_burst_length;
1380255570Strasz	is->is_first_burst_length = handoff->idh_first_burst_length;
1381255570Strasz
1382255570Strasz	if (handoff->idh_header_digest == ISCSI_DIGEST_CRC32C)
1383255570Strasz		is->is_conn->ic_header_crc32c = true;
1384255570Strasz	else
1385255570Strasz		is->is_conn->ic_header_crc32c = false;
1386255570Strasz	if (handoff->idh_data_digest == ISCSI_DIGEST_CRC32C)
1387255570Strasz		is->is_conn->ic_data_crc32c = true;
1388255570Strasz	else
1389255570Strasz		is->is_conn->ic_data_crc32c = false;
1390255570Strasz
1391255570Strasz	is->is_cmdsn = 0;
1392265487Strasz	is->is_expcmdsn = 0;
1393265487Strasz	is->is_maxcmdsn = 0;
1394255570Strasz	is->is_waiting_for_iscsid = false;
1395255570Strasz	is->is_login_phase = false;
1396255570Strasz	is->is_timeout = 0;
1397255570Strasz	is->is_connected = true;
1398255570Strasz	is->is_reason[0] = '\0';
1399255570Strasz
1400255570Strasz	ISCSI_SESSION_UNLOCK(is);
1401255570Strasz
1402265526Strasz#ifdef ICL_KERNEL_PROXY
1403265526Strasz	if (handoff->idh_socket != 0) {
1404265526Strasz#endif
1405265526Strasz		/*
1406265526Strasz		 * Handoff without using ICL proxy.
1407265526Strasz		 */
1408265526Strasz		error = icl_conn_handoff(is->is_conn, handoff->idh_socket);
1409265526Strasz		if (error != 0) {
1410265526Strasz			sx_sunlock(&sc->sc_lock);
1411265526Strasz			iscsi_session_terminate(is);
1412265526Strasz			return (error);
1413265526Strasz		}
1414265526Strasz#ifdef ICL_KERNEL_PROXY
1415255570Strasz	}
1416255570Strasz#endif
1417255570Strasz
1418255570Strasz	sx_sunlock(&sc->sc_lock);
1419255570Strasz
1420255570Strasz	if (is->is_sim != NULL) {
1421255570Strasz		/*
1422255570Strasz		 * When reconnecting, there already is SIM allocated for the session.
1423255570Strasz		 */
1424255570Strasz		KASSERT(is->is_simq_frozen, ("reconnect without frozen simq"));
1425255570Strasz		ISCSI_SESSION_LOCK(is);
1426255570Strasz		ISCSI_SESSION_DEBUG(is, "releasing");
1427255570Strasz		xpt_release_simq(is->is_sim, 1);
1428255570Strasz		is->is_simq_frozen = false;
1429255570Strasz		ISCSI_SESSION_UNLOCK(is);
1430255570Strasz
1431255570Strasz	} else {
1432255570Strasz		ISCSI_SESSION_LOCK(is);
1433255570Strasz		is->is_devq = cam_simq_alloc(maxtags);
1434255570Strasz		if (is->is_devq == NULL) {
1435255570Strasz			ISCSI_SESSION_WARN(is, "failed to allocate simq");
1436255570Strasz			iscsi_session_terminate(is);
1437255570Strasz			return (ENOMEM);
1438255570Strasz		}
1439255570Strasz
1440255570Strasz		is->is_sim = cam_sim_alloc(iscsi_action, iscsi_poll, "iscsi",
1441255570Strasz		    is, is->is_id /* unit */, &is->is_lock,
1442268704Smav		    1, maxtags, is->is_devq);
1443255570Strasz		if (is->is_sim == NULL) {
1444255570Strasz			ISCSI_SESSION_UNLOCK(is);
1445255570Strasz			ISCSI_SESSION_WARN(is, "failed to allocate SIM");
1446255570Strasz			cam_simq_free(is->is_devq);
1447255570Strasz			iscsi_session_terminate(is);
1448255570Strasz			return (ENOMEM);
1449255570Strasz		}
1450255570Strasz
1451255570Strasz		error = xpt_bus_register(is->is_sim, NULL, 0);
1452255570Strasz		if (error != 0) {
1453255570Strasz			ISCSI_SESSION_UNLOCK(is);
1454255570Strasz			ISCSI_SESSION_WARN(is, "failed to register bus");
1455255570Strasz			iscsi_session_terminate(is);
1456255570Strasz			return (ENOMEM);
1457255570Strasz		}
1458255570Strasz
1459255570Strasz		error = xpt_create_path(&is->is_path, /*periph*/NULL,
1460255570Strasz		    cam_sim_path(is->is_sim), CAM_TARGET_WILDCARD,
1461255570Strasz		    CAM_LUN_WILDCARD);
1462255570Strasz		if (error != CAM_REQ_CMP) {
1463255570Strasz			ISCSI_SESSION_UNLOCK(is);
1464255570Strasz			ISCSI_SESSION_WARN(is, "failed to create path");
1465255570Strasz			iscsi_session_terminate(is);
1466255570Strasz			return (ENOMEM);
1467255570Strasz		}
1468255570Strasz		ISCSI_SESSION_UNLOCK(is);
1469255570Strasz	}
1470255570Strasz
1471255570Strasz	return (0);
1472255570Strasz}
1473255570Strasz
1474255570Straszstatic int
1475255570Strasziscsi_ioctl_daemon_fail(struct iscsi_softc *sc,
1476255570Strasz    struct iscsi_daemon_fail *fail)
1477255570Strasz{
1478255570Strasz	struct iscsi_session *is;
1479255570Strasz
1480255570Strasz	sx_slock(&sc->sc_lock);
1481255570Strasz
1482255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1483255570Strasz		if (is->is_id == fail->idf_session_id)
1484255570Strasz			break;
1485255570Strasz	}
1486255570Strasz	if (is == NULL) {
1487255570Strasz		sx_sunlock(&sc->sc_lock);
1488255570Strasz		return (ESRCH);
1489255570Strasz	}
1490255570Strasz	ISCSI_SESSION_LOCK(is);
1491255570Strasz	ISCSI_SESSION_DEBUG(is, "iscsid(8) failed: %s",
1492255570Strasz	    fail->idf_reason);
1493255570Strasz	strlcpy(is->is_reason, fail->idf_reason, sizeof(is->is_reason));
1494255570Strasz	//is->is_waiting_for_iscsid = false;
1495255570Strasz	//is->is_login_phase = true;
1496255570Strasz	//iscsi_session_reconnect(is);
1497255570Strasz	ISCSI_SESSION_UNLOCK(is);
1498255570Strasz	sx_sunlock(&sc->sc_lock);
1499255570Strasz
1500255570Strasz	return (0);
1501255570Strasz}
1502255570Strasz
1503255570Strasz#ifdef ICL_KERNEL_PROXY
1504255570Straszstatic int
1505255570Strasziscsi_ioctl_daemon_connect(struct iscsi_softc *sc,
1506255570Strasz    struct iscsi_daemon_connect *idc)
1507255570Strasz{
1508255570Strasz	struct iscsi_session *is;
1509255570Strasz	struct sockaddr *from_sa, *to_sa;
1510255570Strasz	int error;
1511255570Strasz
1512255570Strasz	sx_slock(&sc->sc_lock);
1513255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1514255570Strasz		if (is->is_id == idc->idc_session_id)
1515255570Strasz			break;
1516255570Strasz	}
1517255570Strasz	if (is == NULL) {
1518255570Strasz		sx_sunlock(&sc->sc_lock);
1519255570Strasz		return (ESRCH);
1520255570Strasz	}
1521255570Strasz	sx_sunlock(&sc->sc_lock);
1522255570Strasz
1523255570Strasz	if (idc->idc_from_addrlen > 0) {
1524255570Strasz		error = getsockaddr(&from_sa, (void *)idc->idc_from_addr, idc->idc_from_addrlen);
1525265526Strasz		if (error != 0) {
1526265526Strasz			ISCSI_SESSION_WARN(is,
1527265526Strasz			    "getsockaddr failed with error %d", error);
1528255570Strasz			return (error);
1529265526Strasz		}
1530255570Strasz	} else {
1531255570Strasz		from_sa = NULL;
1532255570Strasz	}
1533255570Strasz	error = getsockaddr(&to_sa, (void *)idc->idc_to_addr, idc->idc_to_addrlen);
1534255570Strasz	if (error != 0) {
1535265526Strasz		ISCSI_SESSION_WARN(is, "getsockaddr failed with error %d",
1536265526Strasz		    error);
1537255570Strasz		free(from_sa, M_SONAME);
1538255570Strasz		return (error);
1539255570Strasz	}
1540255570Strasz
1541255570Strasz	ISCSI_SESSION_LOCK(is);
1542255570Strasz	is->is_waiting_for_iscsid = false;
1543255570Strasz	is->is_login_phase = true;
1544255570Strasz	is->is_timeout = 0;
1545255570Strasz	ISCSI_SESSION_UNLOCK(is);
1546255570Strasz
1547255570Strasz	error = icl_conn_connect(is->is_conn, idc->idc_iser, idc->idc_domain,
1548255570Strasz	    idc->idc_socktype, idc->idc_protocol, from_sa, to_sa);
1549255570Strasz	free(from_sa, M_SONAME);
1550255570Strasz	free(to_sa, M_SONAME);
1551255570Strasz
1552255570Strasz	/*
1553255570Strasz	 * Digests are always disabled during login phase.
1554255570Strasz	 */
1555255570Strasz	is->is_conn->ic_header_crc32c = false;
1556255570Strasz	is->is_conn->ic_data_crc32c = false;
1557255570Strasz
1558255570Strasz	return (error);
1559255570Strasz}
1560255570Strasz
1561255570Straszstatic int
1562255570Strasziscsi_ioctl_daemon_send(struct iscsi_softc *sc,
1563255570Strasz    struct iscsi_daemon_send *ids)
1564255570Strasz{
1565255570Strasz	struct iscsi_session *is;
1566255570Strasz	struct icl_pdu *ip;
1567255570Strasz	size_t datalen;
1568255570Strasz	void *data;
1569255570Strasz	int error;
1570255570Strasz
1571255570Strasz	sx_slock(&sc->sc_lock);
1572255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1573255570Strasz		if (is->is_id == ids->ids_session_id)
1574255570Strasz			break;
1575255570Strasz	}
1576255570Strasz	if (is == NULL) {
1577255570Strasz		sx_sunlock(&sc->sc_lock);
1578255570Strasz		return (ESRCH);
1579255570Strasz	}
1580255570Strasz	sx_sunlock(&sc->sc_lock);
1581255570Strasz
1582255570Strasz	if (is->is_login_phase == false)
1583255570Strasz		return (EBUSY);
1584255570Strasz
1585255570Strasz	if (is->is_terminating || is->is_reconnecting)
1586255570Strasz		return (EIO);
1587255570Strasz
1588255570Strasz	datalen = ids->ids_data_segment_len;
1589255570Strasz	if (datalen > ISCSI_MAX_DATA_SEGMENT_LENGTH)
1590255570Strasz		return (EINVAL);
1591255570Strasz	if (datalen > 0) {
1592255570Strasz		data = malloc(datalen, M_ISCSI, M_WAITOK);
1593255570Strasz		error = copyin(ids->ids_data_segment, data, datalen);
1594255570Strasz		if (error != 0) {
1595255570Strasz			free(data, M_ISCSI);
1596255570Strasz			return (error);
1597255570Strasz		}
1598255570Strasz	}
1599255570Strasz
1600276618Smav	ip = icl_pdu_new(is->is_conn, M_WAITOK);
1601255570Strasz	memcpy(ip->ip_bhs, ids->ids_bhs, sizeof(*ip->ip_bhs));
1602255570Strasz	if (datalen > 0) {
1603255570Strasz		error = icl_pdu_append_data(ip, data, datalen, M_WAITOK);
1604255570Strasz		KASSERT(error == 0, ("icl_pdu_append_data(..., M_WAITOK) failed"));
1605255570Strasz		free(data, M_ISCSI);
1606255570Strasz	}
1607255570Strasz	icl_pdu_queue(ip);
1608255570Strasz
1609255570Strasz	return (0);
1610255570Strasz}
1611255570Strasz
1612255570Straszstatic int
1613255570Strasziscsi_ioctl_daemon_receive(struct iscsi_softc *sc,
1614255570Strasz    struct iscsi_daemon_receive *idr)
1615255570Strasz{
1616255570Strasz	struct iscsi_session *is;
1617255570Strasz	struct icl_pdu *ip;
1618255570Strasz	void *data;
1619255570Strasz
1620255570Strasz	sx_slock(&sc->sc_lock);
1621255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1622255570Strasz		if (is->is_id == idr->idr_session_id)
1623255570Strasz			break;
1624255570Strasz	}
1625255570Strasz	if (is == NULL) {
1626255570Strasz		sx_sunlock(&sc->sc_lock);
1627255570Strasz		return (ESRCH);
1628255570Strasz	}
1629255570Strasz	sx_sunlock(&sc->sc_lock);
1630255570Strasz
1631255570Strasz	if (is->is_login_phase == false)
1632255570Strasz		return (EBUSY);
1633255570Strasz
1634255570Strasz	ISCSI_SESSION_LOCK(is);
1635255570Strasz	while (is->is_login_pdu == NULL &&
1636255570Strasz	    is->is_terminating == false &&
1637255570Strasz	    is->is_reconnecting == false)
1638255570Strasz		cv_wait(&is->is_login_cv, &is->is_lock);
1639255570Strasz	if (is->is_terminating || is->is_reconnecting) {
1640255570Strasz		ISCSI_SESSION_UNLOCK(is);
1641255570Strasz		return (EIO);
1642255570Strasz	}
1643255570Strasz	ip = is->is_login_pdu;
1644255570Strasz	is->is_login_pdu = NULL;
1645255570Strasz	ISCSI_SESSION_UNLOCK(is);
1646255570Strasz
1647255570Strasz	if (ip->ip_data_len > idr->idr_data_segment_len) {
1648255570Strasz		icl_pdu_free(ip);
1649255570Strasz		return (EMSGSIZE);
1650255570Strasz	}
1651255570Strasz
1652255570Strasz	copyout(ip->ip_bhs, idr->idr_bhs, sizeof(*ip->ip_bhs));
1653255570Strasz	if (ip->ip_data_len > 0) {
1654255570Strasz		data = malloc(ip->ip_data_len, M_ISCSI, M_WAITOK);
1655255570Strasz		icl_pdu_get_data(ip, 0, data, ip->ip_data_len);
1656255570Strasz		copyout(data, idr->idr_data_segment, ip->ip_data_len);
1657255570Strasz		free(data, M_ISCSI);
1658255570Strasz	}
1659255570Strasz
1660255570Strasz	icl_pdu_free(ip);
1661255570Strasz
1662255570Strasz	return (0);
1663255570Strasz}
1664255570Strasz#endif /* ICL_KERNEL_PROXY */
1665255570Strasz
1666255570Straszstatic void
1667255570Strasziscsi_sanitize_session_conf(struct iscsi_session_conf *isc)
1668255570Strasz{
1669255570Strasz	/*
1670255570Strasz	 * Just make sure all the fields are null-terminated.
1671255570Strasz	 *
1672255570Strasz	 * XXX: This is not particularly secure.  We should
1673255570Strasz	 * 	create our own conf and then copy in relevant
1674255570Strasz	 * 	fields.
1675255570Strasz	 */
1676255570Strasz	isc->isc_initiator[ISCSI_NAME_LEN - 1] = '\0';
1677255570Strasz	isc->isc_initiator_addr[ISCSI_ADDR_LEN - 1] = '\0';
1678255570Strasz	isc->isc_initiator_alias[ISCSI_ALIAS_LEN - 1] = '\0';
1679255570Strasz	isc->isc_target[ISCSI_NAME_LEN - 1] = '\0';
1680255570Strasz	isc->isc_target_addr[ISCSI_ADDR_LEN - 1] = '\0';
1681255570Strasz	isc->isc_user[ISCSI_NAME_LEN - 1] = '\0';
1682255570Strasz	isc->isc_secret[ISCSI_SECRET_LEN - 1] = '\0';
1683255570Strasz	isc->isc_mutual_user[ISCSI_NAME_LEN - 1] = '\0';
1684255570Strasz	isc->isc_mutual_secret[ISCSI_SECRET_LEN - 1] = '\0';
1685255570Strasz}
1686255570Strasz
1687269065Smavstatic bool
1688269065Smaviscsi_valid_session_conf(const struct iscsi_session_conf *isc)
1689269065Smav{
1690269065Smav
1691269065Smav	if (isc->isc_initiator[0] == '\0') {
1692269065Smav		ISCSI_DEBUG("empty isc_initiator");
1693269065Smav		return (false);
1694269065Smav	}
1695269065Smav
1696269065Smav	if (isc->isc_target_addr[0] == '\0') {
1697269065Smav		ISCSI_DEBUG("empty isc_target_addr");
1698269065Smav		return (false);
1699269065Smav	}
1700269065Smav
1701269065Smav	if (isc->isc_discovery != 0 && isc->isc_target[0] != 0) {
1702269065Smav		ISCSI_DEBUG("non-empty isc_target for discovery session");
1703269065Smav		return (false);
1704269065Smav	}
1705269065Smav
1706269065Smav	if (isc->isc_discovery == 0 && isc->isc_target[0] == 0) {
1707269065Smav		ISCSI_DEBUG("empty isc_target for non-discovery session");
1708269065Smav		return (false);
1709269065Smav	}
1710269065Smav
1711269065Smav	return (true);
1712269065Smav}
1713269065Smav
1714255570Straszstatic int
1715255570Strasziscsi_ioctl_session_add(struct iscsi_softc *sc, struct iscsi_session_add *isa)
1716255570Strasz{
1717255570Strasz	struct iscsi_session *is;
1718255570Strasz	const struct iscsi_session *is2;
1719255570Strasz	int error;
1720255570Strasz
1721255570Strasz	iscsi_sanitize_session_conf(&isa->isa_conf);
1722269065Smav	if (iscsi_valid_session_conf(&isa->isa_conf) == false)
1723269065Smav		return (EINVAL);
1724255570Strasz
1725255570Strasz	is = malloc(sizeof(*is), M_ISCSI, M_ZERO | M_WAITOK);
1726255570Strasz	memcpy(&is->is_conf, &isa->isa_conf, sizeof(is->is_conf));
1727255570Strasz
1728255570Strasz	sx_xlock(&sc->sc_lock);
1729255570Strasz
1730255570Strasz	/*
1731255570Strasz	 * Prevent duplicates.
1732255570Strasz	 */
1733255570Strasz	TAILQ_FOREACH(is2, &sc->sc_sessions, is_next) {
1734255678Strasz		if (!!is->is_conf.isc_discovery !=
1735255678Strasz		    !!is2->is_conf.isc_discovery)
1736255678Strasz			continue;
1737255678Strasz
1738255678Strasz		if (strcmp(is->is_conf.isc_target_addr,
1739255678Strasz		    is2->is_conf.isc_target_addr) != 0)
1740255678Strasz			continue;
1741255678Strasz
1742255678Strasz		if (is->is_conf.isc_discovery == 0 &&
1743255678Strasz		    strcmp(is->is_conf.isc_target,
1744255678Strasz		    is2->is_conf.isc_target) != 0)
1745255678Strasz			continue;
1746255678Strasz
1747255678Strasz		sx_xunlock(&sc->sc_lock);
1748255678Strasz		free(is, M_ISCSI);
1749255678Strasz		return (EBUSY);
1750255570Strasz	}
1751255570Strasz
1752265496Strasz	is->is_conn = icl_conn_new("iscsi", &is->is_lock);
1753255570Strasz	is->is_conn->ic_receive = iscsi_receive_callback;
1754255570Strasz	is->is_conn->ic_error = iscsi_error_callback;
1755255570Strasz	is->is_conn->ic_prv0 = is;
1756255570Strasz	TAILQ_INIT(&is->is_outstanding);
1757265500Strasz	STAILQ_INIT(&is->is_postponed);
1758255570Strasz	mtx_init(&is->is_lock, "iscsi_lock", NULL, MTX_DEF);
1759255570Strasz	cv_init(&is->is_maintenance_cv, "iscsi_mt");
1760255570Strasz#ifdef ICL_KERNEL_PROXY
1761255570Strasz	cv_init(&is->is_login_cv, "iscsi_login");
1762255570Strasz#endif
1763255570Strasz
1764255570Strasz	is->is_softc = sc;
1765255570Strasz	sc->sc_last_session_id++;
1766255570Strasz	is->is_id = sc->sc_last_session_id;
1767268703Smav	is->is_isid[0] = 0x80; /* RFC 3720, 10.12.5: 10b, "Random" ISID. */
1768268703Smav	arc4rand(&is->is_isid[1], 5, 0);
1769268703Smav	is->is_tsih = 0;
1770255570Strasz	callout_init(&is->is_callout, 1);
1771255570Strasz
1772255570Strasz	error = kthread_add(iscsi_maintenance_thread, is, NULL, NULL, 0, 0, "iscsimt");
1773255570Strasz	if (error != 0) {
1774255570Strasz		ISCSI_SESSION_WARN(is, "kthread_add(9) failed with error %d", error);
1775282954Strasz		sx_xunlock(&sc->sc_lock);
1776255570Strasz		return (error);
1777255570Strasz	}
1778255570Strasz
1779282954Strasz	callout_reset(&is->is_callout, 1 * hz, iscsi_callout, is);
1780282954Strasz	TAILQ_INSERT_TAIL(&sc->sc_sessions, is, is_next);
1781282954Strasz
1782255570Strasz	/*
1783255570Strasz	 * Trigger immediate reconnection.
1784255570Strasz	 */
1785265521Strasz	ISCSI_SESSION_LOCK(is);
1786255570Strasz	is->is_waiting_for_iscsid = true;
1787255570Strasz	strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason));
1788265521Strasz	ISCSI_SESSION_UNLOCK(is);
1789255570Strasz	cv_signal(&sc->sc_cv);
1790255570Strasz
1791255570Strasz	sx_xunlock(&sc->sc_lock);
1792255570Strasz
1793255570Strasz	return (0);
1794255570Strasz}
1795255570Strasz
1796255570Straszstatic bool
1797255570Strasziscsi_session_conf_matches(unsigned int id1, const struct iscsi_session_conf *c1,
1798255570Strasz    unsigned int id2, const struct iscsi_session_conf *c2)
1799255570Strasz{
1800274546Strasz
1801274546Strasz	if (id2 != 0 && id2 != id1)
1802274546Strasz		return (false);
1803255570Strasz	if (c2->isc_target[0] != '\0' &&
1804274546Strasz	    strcmp(c1->isc_target, c2->isc_target) != 0)
1805274546Strasz		return (false);
1806255570Strasz	if (c2->isc_target_addr[0] != '\0' &&
1807274546Strasz	    strcmp(c1->isc_target_addr, c2->isc_target_addr) != 0)
1808274546Strasz		return (false);
1809274546Strasz	return (true);
1810255570Strasz}
1811255570Strasz
1812255570Straszstatic int
1813255570Strasziscsi_ioctl_session_remove(struct iscsi_softc *sc,
1814255570Strasz    struct iscsi_session_remove *isr)
1815255570Strasz{
1816255570Strasz	struct iscsi_session *is, *tmp;
1817255570Strasz	bool found = false;
1818255570Strasz
1819255570Strasz	iscsi_sanitize_session_conf(&isr->isr_conf);
1820255570Strasz
1821255570Strasz	sx_xlock(&sc->sc_lock);
1822255570Strasz	TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) {
1823255570Strasz		ISCSI_SESSION_LOCK(is);
1824255570Strasz		if (iscsi_session_conf_matches(is->is_id, &is->is_conf,
1825255570Strasz		    isr->isr_session_id, &isr->isr_conf)) {
1826255570Strasz			found = true;
1827255570Strasz			iscsi_session_logout(is);
1828255570Strasz			iscsi_session_terminate(is);
1829255570Strasz		}
1830255570Strasz		ISCSI_SESSION_UNLOCK(is);
1831255570Strasz	}
1832255570Strasz	sx_xunlock(&sc->sc_lock);
1833255570Strasz
1834255570Strasz	if (!found)
1835255570Strasz		return (ESRCH);
1836255570Strasz
1837255570Strasz	return (0);
1838255570Strasz}
1839255570Strasz
1840255570Straszstatic int
1841255570Strasziscsi_ioctl_session_list(struct iscsi_softc *sc, struct iscsi_session_list *isl)
1842255570Strasz{
1843255570Strasz	int error;
1844255570Strasz	unsigned int i = 0;
1845255570Strasz	struct iscsi_session *is;
1846255570Strasz	struct iscsi_session_state iss;
1847255570Strasz
1848255570Strasz	sx_slock(&sc->sc_lock);
1849255570Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1850255570Strasz		if (i >= isl->isl_nentries) {
1851255570Strasz			sx_sunlock(&sc->sc_lock);
1852255570Strasz			return (EMSGSIZE);
1853255570Strasz		}
1854255570Strasz		memset(&iss, 0, sizeof(iss));
1855255570Strasz		memcpy(&iss.iss_conf, &is->is_conf, sizeof(iss.iss_conf));
1856255570Strasz		iss.iss_id = is->is_id;
1857255570Strasz		strlcpy(iss.iss_target_alias, is->is_target_alias, sizeof(iss.iss_target_alias));
1858255570Strasz		strlcpy(iss.iss_reason, is->is_reason, sizeof(iss.iss_reason));
1859255570Strasz
1860255570Strasz		if (is->is_conn->ic_header_crc32c)
1861255570Strasz			iss.iss_header_digest = ISCSI_DIGEST_CRC32C;
1862255570Strasz		else
1863255570Strasz			iss.iss_header_digest = ISCSI_DIGEST_NONE;
1864255570Strasz
1865255570Strasz		if (is->is_conn->ic_data_crc32c)
1866255570Strasz			iss.iss_data_digest = ISCSI_DIGEST_CRC32C;
1867255570Strasz		else
1868255570Strasz			iss.iss_data_digest = ISCSI_DIGEST_NONE;
1869255570Strasz
1870255570Strasz		iss.iss_max_data_segment_length = is->is_max_data_segment_length;
1871255570Strasz		iss.iss_immediate_data = is->is_immediate_data;
1872255570Strasz		iss.iss_connected = is->is_connected;
1873255570Strasz
1874255570Strasz		error = copyout(&iss, isl->isl_pstates + i, sizeof(iss));
1875255570Strasz		if (error != 0) {
1876255570Strasz			sx_sunlock(&sc->sc_lock);
1877255570Strasz			return (error);
1878255570Strasz		}
1879255570Strasz		i++;
1880255570Strasz	}
1881255570Strasz	sx_sunlock(&sc->sc_lock);
1882255570Strasz
1883255570Strasz	isl->isl_nentries = i;
1884255570Strasz
1885255570Strasz	return (0);
1886255570Strasz}
1887269065Smav
1888255570Straszstatic int
1889269065Smaviscsi_ioctl_session_modify(struct iscsi_softc *sc,
1890269065Smav    struct iscsi_session_modify *ism)
1891269065Smav{
1892269065Smav	struct iscsi_session *is;
1893269065Smav
1894269065Smav	iscsi_sanitize_session_conf(&ism->ism_conf);
1895269065Smav	if (iscsi_valid_session_conf(&ism->ism_conf) == false)
1896269065Smav		return (EINVAL);
1897269065Smav
1898269065Smav	sx_xlock(&sc->sc_lock);
1899269065Smav	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
1900269065Smav		ISCSI_SESSION_LOCK(is);
1901269065Smav		if (is->is_id == ism->ism_session_id)
1902269065Smav			break;
1903269065Smav		ISCSI_SESSION_UNLOCK(is);
1904269065Smav	}
1905269065Smav	if (is == NULL) {
1906269065Smav		sx_xunlock(&sc->sc_lock);
1907269065Smav		return (ESRCH);
1908269065Smav	}
1909269065Smav	sx_xunlock(&sc->sc_lock);
1910269065Smav
1911269065Smav	memcpy(&is->is_conf, &ism->ism_conf, sizeof(is->is_conf));
1912269065Smav	ISCSI_SESSION_UNLOCK(is);
1913269065Smav
1914269065Smav	iscsi_session_reconnect(is);
1915269065Smav
1916269065Smav	return (0);
1917269065Smav}
1918269065Smav
1919269065Smavstatic int
1920255570Strasziscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode,
1921255570Strasz    struct thread *td)
1922255570Strasz{
1923255570Strasz	struct iscsi_softc *sc;
1924255570Strasz
1925255570Strasz	sc = dev->si_drv1;
1926255570Strasz
1927255570Strasz	switch (cmd) {
1928255570Strasz	case ISCSIDWAIT:
1929255570Strasz		return (iscsi_ioctl_daemon_wait(sc,
1930255570Strasz		    (struct iscsi_daemon_request *)arg));
1931255570Strasz	case ISCSIDHANDOFF:
1932255570Strasz		return (iscsi_ioctl_daemon_handoff(sc,
1933255570Strasz		    (struct iscsi_daemon_handoff *)arg));
1934255570Strasz	case ISCSIDFAIL:
1935255570Strasz		return (iscsi_ioctl_daemon_fail(sc,
1936255570Strasz		    (struct iscsi_daemon_fail *)arg));
1937255570Strasz#ifdef ICL_KERNEL_PROXY
1938255570Strasz	case ISCSIDCONNECT:
1939255570Strasz		return (iscsi_ioctl_daemon_connect(sc,
1940255570Strasz		    (struct iscsi_daemon_connect *)arg));
1941255570Strasz	case ISCSIDSEND:
1942255570Strasz		return (iscsi_ioctl_daemon_send(sc,
1943255570Strasz		    (struct iscsi_daemon_send *)arg));
1944255570Strasz	case ISCSIDRECEIVE:
1945255570Strasz		return (iscsi_ioctl_daemon_receive(sc,
1946255570Strasz		    (struct iscsi_daemon_receive *)arg));
1947255570Strasz#endif /* ICL_KERNEL_PROXY */
1948255570Strasz	case ISCSISADD:
1949255570Strasz		return (iscsi_ioctl_session_add(sc,
1950255570Strasz		    (struct iscsi_session_add *)arg));
1951255570Strasz	case ISCSISREMOVE:
1952255570Strasz		return (iscsi_ioctl_session_remove(sc,
1953255570Strasz		    (struct iscsi_session_remove *)arg));
1954255570Strasz	case ISCSISLIST:
1955255570Strasz		return (iscsi_ioctl_session_list(sc,
1956255570Strasz		    (struct iscsi_session_list *)arg));
1957269065Smav	case ISCSISMODIFY:
1958269065Smav		return (iscsi_ioctl_session_modify(sc,
1959269065Smav		    (struct iscsi_session_modify *)arg));
1960255570Strasz	default:
1961255570Strasz		return (EINVAL);
1962255570Strasz	}
1963255570Strasz}
1964255570Strasz
1965255570Straszstatic uint64_t
1966255570Strasziscsi_encode_lun(uint32_t lun)
1967255570Strasz{
1968255570Strasz	uint8_t encoded[8];
1969255570Strasz	uint64_t result;
1970255570Strasz
1971255570Strasz	memset(encoded, 0, sizeof(encoded));
1972255570Strasz
1973255570Strasz	if (lun < 256) {
1974255570Strasz		/*
1975255570Strasz		 * Peripheral device addressing.
1976255570Strasz		 */
1977255570Strasz		encoded[1] = lun;
1978255570Strasz	} else if (lun < 16384) {
1979255570Strasz		/*
1980255570Strasz		 * Flat space addressing.
1981255570Strasz		 */
1982255570Strasz		encoded[0] = 0x40;
1983255570Strasz		encoded[0] |= (lun >> 8) & 0x3f;
1984255570Strasz		encoded[1] = lun & 0xff;
1985255570Strasz	} else {
1986255570Strasz		/*
1987255570Strasz		 * Extended flat space addressing.
1988255570Strasz		 */
1989255570Strasz		encoded[0] = 0xd2;
1990255570Strasz		encoded[1] = lun >> 16;
1991255570Strasz		encoded[2] = lun >> 8;
1992255570Strasz		encoded[3] = lun;
1993255570Strasz	}
1994255570Strasz
1995255570Strasz	memcpy(&result, encoded, sizeof(result));
1996255570Strasz	return (result);
1997255570Strasz}
1998255570Strasz
1999255570Straszstatic struct iscsi_outstanding *
2000255570Strasziscsi_outstanding_find(struct iscsi_session *is, uint32_t initiator_task_tag)
2001255570Strasz{
2002255570Strasz	struct iscsi_outstanding *io;
2003255570Strasz
2004255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
2005255570Strasz
2006255570Strasz	TAILQ_FOREACH(io, &is->is_outstanding, io_next) {
2007255570Strasz		if (io->io_initiator_task_tag == initiator_task_tag)
2008255570Strasz			return (io);
2009255570Strasz	}
2010255570Strasz	return (NULL);
2011255570Strasz}
2012255570Strasz
2013268705Smavstatic struct iscsi_outstanding *
2014268705Smaviscsi_outstanding_find_ccb(struct iscsi_session *is, union ccb *ccb)
2015268705Smav{
2016268705Smav	struct iscsi_outstanding *io;
2017268705Smav
2018268705Smav	ISCSI_SESSION_LOCK_ASSERT(is);
2019268705Smav
2020268705Smav	TAILQ_FOREACH(io, &is->is_outstanding, io_next) {
2021268705Smav		if (io->io_ccb == ccb)
2022268705Smav			return (io);
2023268705Smav	}
2024268705Smav	return (NULL);
2025268705Smav}
2026268705Smav
2027268705Smavstatic struct iscsi_outstanding *
2028255570Strasziscsi_outstanding_add(struct iscsi_session *is,
2029255570Strasz    uint32_t initiator_task_tag, union ccb *ccb)
2030255570Strasz{
2031255570Strasz	struct iscsi_outstanding *io;
2032255570Strasz
2033255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
2034255570Strasz
2035255570Strasz	KASSERT(iscsi_outstanding_find(is, initiator_task_tag) == NULL,
2036255570Strasz	    ("initiator_task_tag 0x%x already added", initiator_task_tag));
2037255570Strasz
2038255570Strasz	io = uma_zalloc(iscsi_outstanding_zone, M_NOWAIT | M_ZERO);
2039255570Strasz	if (io == NULL) {
2040255570Strasz		ISCSI_SESSION_WARN(is, "failed to allocate %zd bytes", sizeof(*io));
2041268705Smav		return (NULL);
2042255570Strasz	}
2043255570Strasz	io->io_initiator_task_tag = initiator_task_tag;
2044255570Strasz	io->io_ccb = ccb;
2045255570Strasz	TAILQ_INSERT_TAIL(&is->is_outstanding, io, io_next);
2046268705Smav	return (io);
2047255570Strasz}
2048255570Strasz
2049255570Straszstatic void
2050255570Strasziscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io)
2051255570Strasz{
2052255570Strasz
2053255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
2054255570Strasz
2055255570Strasz	TAILQ_REMOVE(&is->is_outstanding, io, io_next);
2056255570Strasz	uma_zfree(iscsi_outstanding_zone, io);
2057255570Strasz}
2058255570Strasz
2059255570Straszstatic void
2060268705Smaviscsi_action_abort(struct iscsi_session *is, union ccb *ccb)
2061268705Smav{
2062268705Smav	struct icl_pdu *request;
2063268705Smav	struct iscsi_bhs_task_management_request *bhstmr;
2064268705Smav	struct ccb_abort *cab = &ccb->cab;
2065268705Smav	struct iscsi_outstanding *io, *aio;
2066268705Smav
2067268705Smav	ISCSI_SESSION_LOCK_ASSERT(is);
2068268705Smav
2069268705Smav#if 0
2070268705Smav	KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__));
2071268705Smav#else
2072268705Smav	if (is->is_login_phase) {
2073268705Smav		ccb->ccb_h.status = CAM_REQ_ABORTED;
2074268705Smav		xpt_done(ccb);
2075268705Smav		return;
2076268705Smav	}
2077268705Smav#endif
2078268705Smav
2079268705Smav	aio = iscsi_outstanding_find_ccb(is, cab->abort_ccb);
2080268705Smav	if (aio == NULL) {
2081268705Smav		ccb->ccb_h.status = CAM_REQ_CMP;
2082268705Smav		xpt_done(ccb);
2083268705Smav		return;
2084268705Smav	}
2085268705Smav
2086276618Smav	request = icl_pdu_new(is->is_conn, M_NOWAIT);
2087268705Smav	if (request == NULL) {
2088268705Smav		ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
2089268705Smav		xpt_done(ccb);
2090268705Smav		return;
2091268705Smav	}
2092268705Smav
2093268705Smav	bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs;
2094268705Smav	bhstmr->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_REQUEST;
2095268705Smav	bhstmr->bhstmr_function = 0x80 | BHSTMR_FUNCTION_ABORT_TASK;
2096268705Smav
2097268707Smav	bhstmr->bhstmr_lun = iscsi_encode_lun(ccb->ccb_h.target_lun);
2098268705Smav	bhstmr->bhstmr_initiator_task_tag = is->is_initiator_task_tag;
2099268705Smav	is->is_initiator_task_tag++;
2100268705Smav	bhstmr->bhstmr_referenced_task_tag = aio->io_initiator_task_tag;
2101268705Smav
2102268705Smav	io = iscsi_outstanding_add(is, bhstmr->bhstmr_initiator_task_tag, NULL);
2103268705Smav	if (io == NULL) {
2104268705Smav		icl_pdu_free(request);
2105268705Smav		ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
2106268705Smav		xpt_done(ccb);
2107268705Smav		return;
2108268705Smav	}
2109268705Smav	io->io_datasn = aio->io_initiator_task_tag;
2110268705Smav	iscsi_pdu_queue_locked(request);
2111268705Smav}
2112268705Smav
2113268705Smavstatic void
2114255570Strasziscsi_action_scsiio(struct iscsi_session *is, union ccb *ccb)
2115255570Strasz{
2116255570Strasz	struct icl_pdu *request;
2117255570Strasz	struct iscsi_bhs_scsi_command *bhssc;
2118255570Strasz	struct ccb_scsiio *csio;
2119268705Smav	struct iscsi_outstanding *io;
2120255570Strasz	size_t len;
2121255570Strasz	int error;
2122255570Strasz
2123255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
2124255570Strasz
2125255570Strasz#if 0
2126255570Strasz	KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__));
2127255570Strasz#else
2128255570Strasz	if (is->is_login_phase) {
2129255570Strasz		ISCSI_SESSION_DEBUG(is, "called during login phase");
2130255570Strasz		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
2131255570Strasz			xpt_freeze_devq(ccb->ccb_h.path, 1);
2132255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
2133255570Strasz		}
2134255570Strasz		ccb->ccb_h.status = CAM_REQ_ABORTED | CAM_DEV_QFRZN;
2135255570Strasz		xpt_done(ccb);
2136255570Strasz		return;
2137255570Strasz	}
2138255570Strasz#endif
2139255570Strasz
2140276618Smav	request = icl_pdu_new(is->is_conn, M_NOWAIT);
2141255570Strasz	if (request == NULL) {
2142255570Strasz		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
2143255570Strasz			xpt_freeze_devq(ccb->ccb_h.path, 1);
2144255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
2145255570Strasz		}
2146255570Strasz		ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
2147255570Strasz		xpt_done(ccb);
2148255570Strasz		return;
2149255570Strasz	}
2150255570Strasz
2151255570Strasz	csio = &ccb->csio;
2152255570Strasz	bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs;
2153255570Strasz	bhssc->bhssc_opcode = ISCSI_BHS_OPCODE_SCSI_COMMAND;
2154255570Strasz	bhssc->bhssc_flags |= BHSSC_FLAGS_F;
2155255570Strasz	switch (csio->ccb_h.flags & CAM_DIR_MASK) {
2156255570Strasz	case CAM_DIR_IN:
2157255570Strasz		bhssc->bhssc_flags |= BHSSC_FLAGS_R;
2158255570Strasz		break;
2159255570Strasz	case CAM_DIR_OUT:
2160255570Strasz		bhssc->bhssc_flags |= BHSSC_FLAGS_W;
2161255570Strasz		break;
2162255570Strasz	}
2163255570Strasz
2164268704Smav	if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) {
2165268704Smav		switch (csio->tag_action) {
2166268704Smav		case MSG_HEAD_OF_Q_TAG:
2167268704Smav			bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_HOQ;
2168268704Smav			break;
2169268704Smav		case MSG_ORDERED_Q_TAG:
2170268704Smav			bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ORDERED;
2171268704Smav			break;
2172268704Smav		case MSG_ACA_TASK:
2173268704Smav			bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ACA;
2174268704Smav			break;
2175268704Smav		case MSG_SIMPLE_Q_TAG:
2176268704Smav		default:
2177268704Smav			bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_SIMPLE;
2178268704Smav			break;
2179268704Smav		}
2180268704Smav	} else
2181268704Smav		bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_UNTAGGED;
2182255570Strasz
2183255570Strasz	bhssc->bhssc_lun = iscsi_encode_lun(csio->ccb_h.target_lun);
2184255570Strasz	bhssc->bhssc_initiator_task_tag = is->is_initiator_task_tag;
2185255570Strasz	is->is_initiator_task_tag++;
2186255570Strasz	bhssc->bhssc_expected_data_transfer_length = htonl(csio->dxfer_len);
2187255570Strasz	KASSERT(csio->cdb_len <= sizeof(bhssc->bhssc_cdb),
2188255570Strasz	    ("unsupported CDB size %zd", (size_t)csio->cdb_len));
2189255570Strasz
2190255570Strasz	if (csio->ccb_h.flags & CAM_CDB_POINTER)
2191255570Strasz		memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_ptr, csio->cdb_len);
2192255570Strasz	else
2193255570Strasz		memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_bytes, csio->cdb_len);
2194255570Strasz
2195268705Smav	io = iscsi_outstanding_add(is, bhssc->bhssc_initiator_task_tag, ccb);
2196268705Smav	if (io == NULL) {
2197255570Strasz		icl_pdu_free(request);
2198255570Strasz		if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
2199255570Strasz			xpt_freeze_devq(ccb->ccb_h.path, 1);
2200255570Strasz			ISCSI_SESSION_DEBUG(is, "freezing devq");
2201255570Strasz		}
2202255570Strasz		ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
2203255570Strasz		xpt_done(ccb);
2204255570Strasz		return;
2205255570Strasz	}
2206255570Strasz
2207255570Strasz	if (is->is_immediate_data &&
2208255570Strasz	    (csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
2209255570Strasz		len = csio->dxfer_len;
2210255570Strasz		//ISCSI_SESSION_DEBUG(is, "adding %zd of immediate data", len);
2211255570Strasz		if (len > is->is_first_burst_length) {
2212255570Strasz			ISCSI_SESSION_DEBUG(is, "len %zd -> %zd", len, is->is_first_burst_length);
2213255570Strasz			len = is->is_first_burst_length;
2214255570Strasz		}
2215276234Smav		if (len > is->is_max_data_segment_length) {
2216276234Smav			ISCSI_SESSION_DEBUG(is, "len %zd -> %zd", len, is->is_max_data_segment_length);
2217276234Smav			len = is->is_max_data_segment_length;
2218276234Smav		}
2219255570Strasz
2220255570Strasz		error = icl_pdu_append_data(request, csio->data_ptr, len, M_NOWAIT);
2221255570Strasz		if (error != 0) {
2222255570Strasz			icl_pdu_free(request);
2223255570Strasz			if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) {
2224255570Strasz				xpt_freeze_devq(ccb->ccb_h.path, 1);
2225255570Strasz				ISCSI_SESSION_DEBUG(is, "freezing devq");
2226255570Strasz			}
2227255570Strasz			ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN;
2228255570Strasz			xpt_done(ccb);
2229255570Strasz			return;
2230255570Strasz		}
2231255570Strasz	}
2232255570Strasz	iscsi_pdu_queue_locked(request);
2233255570Strasz}
2234255570Strasz
2235255570Straszstatic void
2236255570Strasziscsi_action(struct cam_sim *sim, union ccb *ccb)
2237255570Strasz{
2238255570Strasz	struct iscsi_session *is;
2239255570Strasz
2240255570Strasz	is = cam_sim_softc(sim);
2241255570Strasz
2242255570Strasz	ISCSI_SESSION_LOCK_ASSERT(is);
2243255570Strasz
2244265523Strasz	if (is->is_terminating ||
2245265523Strasz	    (is->is_connected == false && fail_on_disconnection)) {
2246255570Strasz		ccb->ccb_h.status = CAM_DEV_NOT_THERE;
2247255570Strasz		xpt_done(ccb);
2248255570Strasz		return;
2249255570Strasz	}
2250255570Strasz
2251255570Strasz	switch (ccb->ccb_h.func_code) {
2252255570Strasz	case XPT_PATH_INQ:
2253255570Strasz	{
2254255570Strasz		struct ccb_pathinq *cpi = &ccb->cpi;
2255255570Strasz
2256255570Strasz		cpi->version_num = 1;
2257255570Strasz		cpi->hba_inquiry = PI_TAG_ABLE;
2258255570Strasz		cpi->target_sprt = 0;
2259255570Strasz		//cpi->hba_misc = PIM_NOBUSRESET;
2260255570Strasz		cpi->hba_misc = 0;
2261255570Strasz		cpi->hba_eng_cnt = 0;
2262255570Strasz		cpi->max_target = 0;
2263255570Strasz		cpi->max_lun = 255;
2264255570Strasz		//cpi->initiator_id = 0; /* XXX */
2265255570Strasz		cpi->initiator_id = 64; /* XXX */
2266255678Strasz		strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
2267255678Strasz		strlcpy(cpi->hba_vid, "iSCSI", HBA_IDLEN);
2268255678Strasz		strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
2269255570Strasz		cpi->unit_number = cam_sim_unit(sim);
2270255570Strasz		cpi->bus_id = cam_sim_bus(sim);
2271255570Strasz		cpi->base_transfer_speed = 150000; /* XXX */
2272255570Strasz		cpi->transport = XPORT_ISCSI;
2273255570Strasz		cpi->transport_version = 0;
2274255570Strasz		cpi->protocol = PROTO_SCSI;
2275255570Strasz		cpi->protocol_version = SCSI_REV_SPC3;
2276255570Strasz		cpi->maxio = MAXPHYS;
2277255570Strasz		cpi->ccb_h.status = CAM_REQ_CMP;
2278255570Strasz		break;
2279255570Strasz	}
2280268704Smav	case XPT_GET_TRAN_SETTINGS:
2281268704Smav	{
2282268704Smav		struct ccb_trans_settings	*cts;
2283268704Smav		struct ccb_trans_settings_scsi	*scsi;
2284268704Smav
2285268704Smav		cts = &ccb->cts;
2286268704Smav		scsi = &cts->proto_specific.scsi;
2287268704Smav
2288268704Smav		cts->protocol = PROTO_SCSI;
2289268704Smav		cts->protocol_version = SCSI_REV_SPC3;
2290268704Smav		cts->transport = XPORT_ISCSI;
2291268704Smav		cts->transport_version = 0;
2292268704Smav		scsi->valid = CTS_SCSI_VALID_TQ;
2293268704Smav		scsi->flags = CTS_SCSI_FLAGS_TAG_ENB;
2294268704Smav		cts->ccb_h.status = CAM_REQ_CMP;
2295268704Smav		break;
2296268704Smav	}
2297255570Strasz	case XPT_CALC_GEOMETRY:
2298255570Strasz		cam_calc_geometry(&ccb->ccg, /*extended*/1);
2299255570Strasz		ccb->ccb_h.status = CAM_REQ_CMP;
2300255570Strasz		break;
2301255570Strasz#if 0
2302255570Strasz	/*
2303255570Strasz	 * XXX: What's the point?
2304255570Strasz	 */
2305255570Strasz	case XPT_RESET_BUS:
2306255570Strasz	case XPT_TERM_IO:
2307255570Strasz		ISCSI_SESSION_DEBUG(is, "faking success for reset, abort, or term_io");
2308255570Strasz		ccb->ccb_h.status = CAM_REQ_CMP;
2309255570Strasz		break;
2310255570Strasz#endif
2311268705Smav	case XPT_ABORT:
2312268705Smav		iscsi_action_abort(is, ccb);
2313268705Smav		return;
2314255570Strasz	case XPT_SCSI_IO:
2315255570Strasz		iscsi_action_scsiio(is, ccb);
2316255570Strasz		return;
2317255570Strasz	default:
2318255570Strasz#if 0
2319255570Strasz		ISCSI_SESSION_DEBUG(is, "got unsupported code 0x%x", ccb->ccb_h.func_code);
2320255570Strasz#endif
2321255570Strasz		ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
2322255570Strasz		break;
2323255570Strasz	}
2324255570Strasz	xpt_done(ccb);
2325255570Strasz}
2326255570Strasz
2327255570Straszstatic void
2328255570Strasziscsi_poll(struct cam_sim *sim)
2329255570Strasz{
2330255570Strasz
2331255570Strasz	KASSERT(0, ("%s: you're not supposed to be here", __func__));
2332255570Strasz}
2333255570Strasz
2334255570Straszstatic void
2335294708Ssmhiscsi_terminate_sessions(struct iscsi_softc *sc)
2336255570Strasz{
2337255570Strasz	struct iscsi_session *is;
2338255570Strasz
2339294708Ssmh	sx_slock(&sc->sc_lock);
2340294708Ssmh	TAILQ_FOREACH(is, &sc->sc_sessions, is_next)
2341294708Ssmh		iscsi_session_terminate(is);
2342294708Ssmh	while(!TAILQ_EMPTY(&sc->sc_sessions)) {
2343294708Ssmh		ISCSI_DEBUG("waiting for sessions to terminate");
2344294708Ssmh		cv_wait(&sc->sc_cv, &sc->sc_lock);
2345294708Ssmh	}
2346294708Ssmh	ISCSI_DEBUG("all sessions terminated");
2347294708Ssmh	sx_sunlock(&sc->sc_lock);
2348294708Ssmh}
2349255570Strasz
2350294708Ssmhstatic void
2351294708Ssmhiscsi_shutdown_pre(struct iscsi_softc *sc)
2352294708Ssmh{
2353294708Ssmh	struct iscsi_session *is;
2354294708Ssmh
2355294708Ssmh	if (!fail_on_shutdown)
2356294708Ssmh		return;
2357294708Ssmh
2358289509Strasz	/*
2359289509Strasz	 * If we have any sessions waiting for reconnection, request
2360289509Strasz	 * maintenance thread to fail them immediately instead of waiting
2361289509Strasz	 * for reconnect timeout.
2362294708Ssmh	 *
2363294708Ssmh	 * This prevents LUNs with mounted filesystems that are supported
2364294708Ssmh	 * by disconnected iSCSI sessions from hanging, however it will
2365294708Ssmh	 * fail all queued BIOs.
2366289509Strasz	 */
2367294708Ssmh	ISCSI_DEBUG("forcing failing all disconnected sessions due to shutdown");
2368294708Ssmh
2369294708Ssmh	fail_on_disconnection = 1;
2370294708Ssmh
2371255570Strasz	sx_slock(&sc->sc_lock);
2372289509Strasz	TAILQ_FOREACH(is, &sc->sc_sessions, is_next) {
2373289509Strasz		ISCSI_SESSION_LOCK(is);
2374294708Ssmh		if (!is->is_connected) {
2375294708Ssmh			ISCSI_SESSION_DEBUG(is, "force failing disconnected session early");
2376289509Strasz			iscsi_session_reconnect(is);
2377294708Ssmh		}
2378289509Strasz		ISCSI_SESSION_UNLOCK(is);
2379289509Strasz	}
2380255570Strasz	sx_sunlock(&sc->sc_lock);
2381255570Strasz}
2382255570Strasz
2383294708Ssmhstatic void
2384294708Ssmhiscsi_shutdown_post(struct iscsi_softc *sc)
2385294708Ssmh{
2386294708Ssmh
2387325226Savg	if (panicstr == NULL) {
2388325226Savg		ISCSI_DEBUG("removing all sessions due to shutdown");
2389325226Savg		iscsi_terminate_sessions(sc);
2390325226Savg	}
2391294708Ssmh}
2392294708Ssmh
2393255570Straszstatic int
2394255570Strasziscsi_load(void)
2395255570Strasz{
2396255570Strasz	int error;
2397255570Strasz
2398255570Strasz	sc = malloc(sizeof(*sc), M_ISCSI, M_ZERO | M_WAITOK);
2399255570Strasz	sx_init(&sc->sc_lock, "iscsi");
2400255570Strasz	TAILQ_INIT(&sc->sc_sessions);
2401255570Strasz	cv_init(&sc->sc_cv, "iscsi_cv");
2402255570Strasz
2403255570Strasz	iscsi_outstanding_zone = uma_zcreate("iscsi_outstanding",
2404255570Strasz	    sizeof(struct iscsi_outstanding), NULL, NULL, NULL, NULL,
2405256058Strasz	    UMA_ALIGN_PTR, 0);
2406255570Strasz
2407255570Strasz	error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &iscsi_cdevsw,
2408255570Strasz	    NULL, UID_ROOT, GID_WHEEL, 0600, "iscsi");
2409255570Strasz	if (error != 0) {
2410255570Strasz		ISCSI_WARN("failed to create device node, error %d", error);
2411255570Strasz		return (error);
2412255570Strasz	}
2413255570Strasz	sc->sc_cdev->si_drv1 = sc;
2414255570Strasz
2415294708Ssmh	sc->sc_shutdown_pre_eh = EVENTHANDLER_REGISTER(shutdown_pre_sync,
2416294708Ssmh	    iscsi_shutdown_pre, sc, SHUTDOWN_PRI_FIRST);
2417294708Ssmh	/*
2418294708Ssmh	 * shutdown_post_sync needs to run after filesystem shutdown and before
2419294708Ssmh	 * CAM shutdown - otherwise when rebooting with an iSCSI session that is
2420294708Ssmh	 * disconnected but has outstanding requests, dashutdown() will hang on
2421294708Ssmh	 * cam_periph_runccb().
2422294708Ssmh	 */
2423294708Ssmh	sc->sc_shutdown_post_eh = EVENTHANDLER_REGISTER(shutdown_post_sync,
2424294708Ssmh	    iscsi_shutdown_post, sc, SHUTDOWN_PRI_DEFAULT - 1);
2425255570Strasz
2426255570Strasz	return (0);
2427255570Strasz}
2428255570Strasz
2429255570Straszstatic int
2430255570Strasziscsi_unload(void)
2431255570Strasz{
2432255570Strasz
2433255857Strasz	if (sc->sc_cdev != NULL) {
2434255857Strasz		ISCSI_DEBUG("removing device node");
2435255857Strasz		destroy_dev(sc->sc_cdev);
2436255857Strasz		ISCSI_DEBUG("device node removed");
2437255857Strasz	}
2438255570Strasz
2439294708Ssmh	if (sc->sc_shutdown_pre_eh != NULL)
2440294708Ssmh		EVENTHANDLER_DEREGISTER(shutdown_pre_sync, sc->sc_shutdown_pre_eh);
2441294708Ssmh	if (sc->sc_shutdown_post_eh != NULL)
2442294708Ssmh		EVENTHANDLER_DEREGISTER(shutdown_post_sync, sc->sc_shutdown_post_eh);
2443255570Strasz
2444294708Ssmh	iscsi_terminate_sessions(sc);
2445255570Strasz
2446255570Strasz	uma_zdestroy(iscsi_outstanding_zone);
2447255570Strasz	sx_destroy(&sc->sc_lock);
2448255570Strasz	cv_destroy(&sc->sc_cv);
2449255570Strasz	free(sc, M_ISCSI);
2450255570Strasz	return (0);
2451255570Strasz}
2452255570Strasz
2453255570Straszstatic int
2454255570Strasziscsi_quiesce(void)
2455255570Strasz{
2456255570Strasz	sx_slock(&sc->sc_lock);
2457255570Strasz	if (!TAILQ_EMPTY(&sc->sc_sessions)) {
2458255570Strasz		sx_sunlock(&sc->sc_lock);
2459255570Strasz		return (EBUSY);
2460255570Strasz	}
2461255570Strasz	sx_sunlock(&sc->sc_lock);
2462255570Strasz	return (0);
2463255570Strasz}
2464255570Strasz
2465255570Straszstatic int
2466255570Strasziscsi_modevent(module_t mod, int what, void *arg)
2467255570Strasz{
2468255570Strasz	int error;
2469255570Strasz
2470255570Strasz	switch (what) {
2471255570Strasz	case MOD_LOAD:
2472255570Strasz		error = iscsi_load();
2473255570Strasz		break;
2474255570Strasz	case MOD_UNLOAD:
2475255570Strasz		error = iscsi_unload();
2476255570Strasz		break;
2477255570Strasz	case MOD_QUIESCE:
2478255570Strasz		error = iscsi_quiesce();
2479255570Strasz		break;
2480255570Strasz	default:
2481255570Strasz		error = EINVAL;
2482255570Strasz		break;
2483255570Strasz	}
2484255570Strasz	return (error);
2485255570Strasz}
2486255570Strasz
2487255570Straszmoduledata_t iscsi_data = {
2488255570Strasz	"iscsi",
2489255570Strasz	iscsi_modevent,
2490255570Strasz	0
2491255570Strasz};
2492255570Strasz
2493255570StraszDECLARE_MODULE(iscsi, iscsi_data, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
2494255570StraszMODULE_DEPEND(iscsi, cam, 1, 1, 1);
2495255570StraszMODULE_DEPEND(iscsi, icl, 1, 1, 1);
2496