From a992abf0413f4cc428d4368e1d82d65f5b3d6397 Mon Sep 17 00:00:00 2001 From: trasz Date: Sat, 14 Sep 2013 15:29:06 +0000 Subject: Bring in the new iSCSI target and initiator. Reviewed by: ken (parts) Approved by: re (delphij) Sponsored by: FreeBSD Foundation --- sys/cam/ctl/ctl.c | 22 + sys/cam/ctl/ctl_frontend_iscsi.c | 2638 ++++++++++++++++++++++++++++++++++++++ sys/cam/ctl/ctl_frontend_iscsi.h | 112 ++ sys/cam/ctl/ctl_ioctl.h | 169 +++ 4 files changed, 2941 insertions(+) create mode 100644 sys/cam/ctl/ctl_frontend_iscsi.c create mode 100644 sys/cam/ctl/ctl_frontend_iscsi.h (limited to 'sys/cam') diff --git a/sys/cam/ctl/ctl.c b/sys/cam/ctl/ctl.c index 215f1cd..853a36d 100644 --- a/sys/cam/ctl/ctl.c +++ b/sys/cam/ctl/ctl.c @@ -3148,6 +3148,28 @@ ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, sbuf_delete(sb); break; } + case CTL_ISCSI: { + struct ctl_iscsi *ci; + struct ctl_frontend *fe; + + ci = (struct ctl_iscsi *)addr; + + mtx_lock(&softc->ctl_lock); + STAILQ_FOREACH(fe, &softc->fe_list, links) { + if (strcmp(fe->port_name, "iscsi") == 0) + break; + } + mtx_unlock(&softc->ctl_lock); + + if (fe == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), "Backend \"iscsi\" not found."); + break; + } + + retval = fe->ioctl(dev, cmd, addr, flag, td); + break; + } default: { /* XXX KDM should we fix this? */ #if 0 diff --git a/sys/cam/ctl/ctl_frontend_iscsi.c b/sys/cam/ctl/ctl_frontend_iscsi.c new file mode 100644 index 0000000..34e2ead --- /dev/null +++ b/sys/cam/ctl/ctl_frontend_iscsi.c @@ -0,0 +1,2638 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * CTL frontend for the iSCSI protocol. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dev/iscsi/icl.h" +#include "../../dev/iscsi/iscsi_proto.h" +#include "ctl_frontend_iscsi.h" + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +static MALLOC_DEFINE(M_CFISCSI, "cfiscsi", "Memory used for CTL iSCSI frontend"); +static uma_zone_t cfiscsi_data_wait_zone; + +SYSCTL_NODE(_kern_cam_ctl, OID_AUTO, iscsi, CTLFLAG_RD, 0, + "CAM Target Layer iSCSI Frontend"); +static int debug = 3; +TUNABLE_INT("kern.cam.ctl.iscsi.debug", &debug); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, debug, CTLFLAG_RW, + &debug, 1, "Enable debug messages"); +static int ping_timeout = 5; +TUNABLE_INT("kern.cam.ctl.iscsi.ping_timeout", &ping_timeout); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RW, + &ping_timeout, 5, "Interval between ping (NOP-Out) requests, in seconds"); +static int login_timeout = 60; +TUNABLE_INT("kern.cam.ctl.iscsi.login_timeout", &login_timeout); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, login_timeout, CTLFLAG_RW, + &login_timeout, 60, "Time to wait for ctld(8) to finish Login Phase, in seconds"); +static int maxcmdsn_delta = 256; +TUNABLE_INT("kern.cam.ctl.iscsi.maxcmdsn_delta", &maxcmdsn_delta); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, maxcmdsn_delta, CTLFLAG_RW, + &maxcmdsn_delta, 256, "Number of commands the initiator can send " + "without confirmation"); + +#define CFISCSI_DEBUG(X, ...) \ + if (debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define CFISCSI_WARN(X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_DEBUG(S, X, ...) \ + if (debug > 1) { \ + printf("%s: %s (%s): " X "\n", \ + __func__, S->cs_initiator_addr, \ + S->cs_initiator_name, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_WARN(S, X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s (%s): " X "\n", \ + S->cs_initiator_addr, \ + S->cs_initiator_name, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_LOCK(X) mtx_lock(&X->cs_lock) +#define CFISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->cs_lock) +#define CFISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->cs_lock, MA_OWNED) + +#define CONN_SESSION(X) ((struct cfiscsi_session *)(X)->ic_prv0) +#define PDU_SESSION(X) CONN_SESSION((X)->ip_conn) +#define PDU_EXPDATASN(X) (X)->ip_prv0 +#define PDU_TOTAL_TRANSFER_LEN(X) (X)->ip_prv1 +#define PDU_R2TSN(X) (X)->ip_prv2 + +int cfiscsi_init(void); +static void cfiscsi_online(void *arg); +static void cfiscsi_offline(void *arg); +static int cfiscsi_targ_enable(void *arg, struct ctl_id targ_id); +static int cfiscsi_targ_disable(void *arg, struct ctl_id targ_id); +static int cfiscsi_lun_enable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_lun_disable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td); +static int cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len); +static void cfiscsi_datamove(union ctl_io *io); +static void cfiscsi_done(union ctl_io *io); +static uint32_t cfiscsi_map_lun(void *arg, uint32_t lun); +static void cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request); +static void cfiscsi_pdu_handle_nop_out(struct icl_pdu *request); +static void cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request); +static void cfiscsi_pdu_handle_task_request(struct icl_pdu *request); +static void cfiscsi_pdu_handle_data_out(struct icl_pdu *request); +static void cfiscsi_pdu_handle_logout_request(struct icl_pdu *request); +static void cfiscsi_session_terminate(struct cfiscsi_session *cs); +static struct cfiscsi_target *cfiscsi_target_find(struct cfiscsi_softc + *softc, const char *name); +static void cfiscsi_target_release(struct cfiscsi_target *ct); +static void cfiscsi_session_delete(struct cfiscsi_session *cs); + +static struct cfiscsi_softc cfiscsi_softc; +extern struct ctl_softc *control_softc; + +static int cfiscsi_module_event_handler(module_t, int /*modeventtype_t*/, void *); + +static moduledata_t cfiscsi_moduledata = { + "ctlcfiscsi", + cfiscsi_module_event_handler, + NULL +}; + +DECLARE_MODULE(ctlcfiscsi, cfiscsi_moduledata, SI_SUB_CONFIGURE, SI_ORDER_FOURTH); +MODULE_VERSION(ctlcfiscsi, 1); +MODULE_DEPEND(ctlcfiscsi, ctl, 1, 1, 1); +MODULE_DEPEND(ctlcfiscsi, icl, 1, 1, 1); + +static struct icl_pdu * +cfiscsi_pdu_new_response(struct icl_pdu *request, int flags) +{ + + return (icl_pdu_new_bhs(request->ip_conn, flags)); +} + +static void +cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request) +{ + const struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + uint32_t cmdsn, expstatsn; + + cs = PDU_SESSION(request); + + /* + * Every incoming PDU - not just NOP-Out - resets the ping timer. + * The purpose of the timeout is to reset the connection when it stalls; + * we don't want this to happen when NOP-In or NOP-Out ends up delayed + * in some queue. + * + * XXX: Locking? + */ + cs->cs_timeout = 0; + + /* + * Data-Out PDUs don't contain CmdSN. + */ + if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) + return; + + /* + * We're only using fields common for all the request + * (initiator -> target) PDUs. + */ + bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_bhs; + cmdsn = ntohl(bhssc->bhssc_cmdsn); + expstatsn = ntohl(bhssc->bhssc_expstatsn); + + CFISCSI_SESSION_LOCK(cs); +#if 0 + if (expstatsn != cs->cs_statsn) { + CFISCSI_SESSION_DEBUG(cs, "received PDU with ExpStatSN %d, " + "while current StatSN is %d", expstatsn, + cs->cs_statsn); + } +#endif + + /* + * XXX: The target MUST silently ignore any non-immediate command + * outside of this range or non-immediate duplicates within + * the range. + */ + if (cmdsn != cs->cs_cmdsn) { + CFISCSI_SESSION_WARN(cs, "received PDU with CmdSN %d, " + "while expected CmdSN was %d", cmdsn, cs->cs_cmdsn); + cs->cs_cmdsn = cmdsn + 1; + CFISCSI_SESSION_UNLOCK(cs); + return; + } + + /* + * XXX: The CmdSN of the rejected command PDU (if it is a non-immediate + * command) MUST NOT be considered received by the target + * (i.e., a command sequence gap must be assumed for the CmdSN) + */ + + if ((request->ip_bhs->bhs_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) + cs->cs_cmdsn++; + + CFISCSI_SESSION_UNLOCK(cs); +} + +static void +cfiscsi_pdu_handle(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + + cfiscsi_pdu_update_cmdsn(request); + + /* + * Handle the PDU; this includes e.g. receiving the remaining + * part of PDU and submitting the SCSI command to CTL + * or queueing a reply. The handling routine is responsible + * for freeing the PDU when it's no longer needed. + */ + switch (request->ip_bhs->bhs_opcode & + ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_NOP_OUT: + cfiscsi_pdu_handle_nop_out(request); + break; + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_pdu_handle_scsi_command(request); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_pdu_handle_task_request(request); + break; + case ISCSI_BHS_OPCODE_SCSI_DATA_OUT: + cfiscsi_pdu_handle_data_out(request); + break; + case ISCSI_BHS_OPCODE_LOGOUT_REQUEST: + cfiscsi_pdu_handle_logout_request(request); + break; + default: + CFISCSI_SESSION_WARN(cs, "received PDU with unsupported " + "opcode 0x%x; dropping connection", + request->ip_bhs->bhs_opcode); + cfiscsi_session_terminate(cs); + icl_pdu_free(request); + } + +} + +static void +cfiscsi_receive_callback(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + +#ifdef ICL_KERNEL_PROXY + if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { + if (cs->cs_login_pdu == NULL) + cs->cs_login_pdu = request; + else + icl_pdu_free(request); + cv_signal(&cs->cs_login_cv); + return; + } +#endif + + cfiscsi_pdu_handle(request); +} + +static void +cfiscsi_error_callback(struct icl_conn *ic) +{ + struct cfiscsi_session *cs; + + cs = CONN_SESSION(ic); + + CFISCSI_SESSION_WARN(cs, "connection error; dropping connection"); + cfiscsi_session_terminate(cs); +} + +static int +cfiscsi_pdu_prepare(struct icl_pdu *response) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_scsi_response *bhssr; + bool advance_statsn = true; + + cs = PDU_SESSION(response); + + CFISCSI_SESSION_LOCK_ASSERT(cs); + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; + + /* + * 10.8.3: "The StatSN for this connection is not advanced + * after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_R2T) + advance_statsn = false; + + /* + * 10.19.2: "However, when the Initiator Task Tag is set to 0xffffffff, + * StatSN for the connection is not advanced after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_NOP_IN && + bhssr->bhssr_initiator_task_tag == 0xffffffff) + advance_statsn = false; + + /* + * See the comment below - StatSN is not meaningful and must + * not be advanced. + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_SCSI_DATA_IN) + advance_statsn = false; + + /* + * 10.7.3: "The fields StatSN, Status, and Residual Count + * only have meaningful content if the S bit is set to 1." + */ + if (bhssr->bhssr_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN) + bhssr->bhssr_statsn = htonl(cs->cs_statsn); + bhssr->bhssr_expcmdsn = htonl(cs->cs_cmdsn); + bhssr->bhssr_maxcmdsn = htonl(cs->cs_cmdsn + maxcmdsn_delta); + + if (advance_statsn) + cs->cs_statsn++; + + return (0); +} + +static void +cfiscsi_pdu_queue(struct icl_pdu *response) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(response); + + CFISCSI_SESSION_LOCK(cs); + cfiscsi_pdu_prepare(response); + icl_pdu_queue(response); + CFISCSI_SESSION_UNLOCK(cs); +} + +static uint32_t +cfiscsi_decode_lun(uint64_t encoded) +{ + uint8_t lun[8]; + uint32_t result; + + /* + * The LUN field in iSCSI PDUs may look like an ordinary 64 bit number, + * but is in fact an evil, multidimensional structure defined + * in SCSI Architecture Model 5 (SAM-5), section 4.6. + */ + memcpy(lun, &encoded, sizeof(lun)); + switch (lun[0] & 0xC0) { + case 0x00: + if ((lun[0] & 0x3f) != 0 || lun[2] != 0 || lun[3] != 0 || + lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN("malformed LUN " + "(peripheral device addressing method): 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + result = lun[1]; + break; + case 0x40: + if (lun[2] != 0 || lun[3] != 0 || lun[4] != 0 || lun[5] != 0 || + lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN("malformed LUN " + "(flat address space addressing method): 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + result = ((lun[0] & 0x3f) << 8) + lun[1]; + break; + case 0xC0: + if (lun[0] != 0xD2 || lun[4] != 0 || lun[5] != 0 || + lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN("malformed LUN (extended flat " + "address space addressing method): 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + result = (lun[1] << 16) + (lun[2] << 8) + lun[3]; + default: + CFISCSI_WARN("unsupported LUN format 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + + return (result); +} + +static void +cfiscsi_pdu_handle_nop_out(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct icl_pdu *response; + + cs = PDU_SESSION(request); + bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs; + + if (bhsno->bhsno_initiator_task_tag == 0xffffffff) { + /* + * Nothing to do, iscsi_pdu_update_statsn() already + * zeroed the timeout. + */ + icl_pdu_free(request); + return; + } + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + return; + } + bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = bhsno->bhsno_initiator_task_tag; + bhsni->bhsni_target_transfer_tag = 0xffffffff; + +#if 0 + /* XXX */ + response->ip_data_len = request->ip_data_len; + response->ip_data_mbuf = request->ip_data_mbuf; + request->ip_data_len = 0; + request->ip_data_mbuf = NULL; +#endif + + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request) +{ + struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = PDU_SESSION(request); + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", + // bhssc->bhssc_initiator_task_tag); + + if (request->ip_data_len > 0 && cs->cs_immediate_data == false) { + CFISCSI_SESSION_WARN(cs, "unsolicited data with " + "ImmediateData=No; dropping connection"); + cfiscsi_session_terminate(cs); + icl_pdu_free(request); + return; + } + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + icl_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_SCSI; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhssc->bhssc_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->scsiio.tag_num = bhssc->bhssc_initiator_task_tag; + switch ((bhssc->bhssc_flags & BHSSC_FLAGS_ATTR)) { + case BHSSC_FLAGS_ATTR_UNTAGGED: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + break; + case BHSSC_FLAGS_ATTR_SIMPLE: + io->scsiio.tag_type = CTL_TAG_SIMPLE; + break; + case BHSSC_FLAGS_ATTR_ORDERED: + io->scsiio.tag_type = CTL_TAG_ORDERED; + break; + case BHSSC_FLAGS_ATTR_HOQ: + io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE; + break; + case BHSSC_FLAGS_ATTR_ACA: + io->scsiio.tag_type = CTL_TAG_ACA; + break; + default: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + CFISCSI_SESSION_WARN(cs, "unhandled tag type %d", + bhssc->bhssc_flags & BHSSC_FLAGS_ATTR); + break; + } + io->scsiio.cdb_len = sizeof(bhssc->bhssc_cdb); /* Which is 16. */ + memcpy(io->scsiio.cdb, bhssc->bhssc_cdb, sizeof(bhssc->bhssc_cdb)); + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + icl_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_task_request(struct icl_pdu *request) +{ + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct icl_pdu *response; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = PDU_SESSION(request); + bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + icl_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhstmr->bhstmr_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + + switch (bhstmr->bhstmr_function & ~0x80) { + case BHSTMR_FUNCTION_ABORT_TASK: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_ABORT_TASK"); +#endif + io->taskio.task_action = CTL_TASK_ABORT_TASK; + io->taskio.tag_num = bhstmr->bhstmr_referenced_task_tag; + break; + case BHSTMR_FUNCTION_LOGICAL_UNIT_RESET: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_LOGICAL_UNIT_RESET"); +#endif + io->taskio.task_action = CTL_TASK_LUN_RESET; + break; + case BHSTMR_FUNCTION_TARGET_COLD_RESET: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_TARGET_COLD_RESET"); +#endif + io->taskio.task_action = CTL_TASK_BUS_RESET; + break; + default: + CFISCSI_SESSION_DEBUG(cs, "unsupported function 0x%x", + bhstmr->bhstmr_function & ~0x80); + ctl_free_io(io); + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + return; + } + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->ip_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + bhstmr2->bhstmr_initiator_task_tag = + bhstmr->bhstmr_initiator_task_tag; + icl_pdu_free(request); + cfiscsi_pdu_queue(response); + return; + } + + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + icl_pdu_free(request); + } +} + +static bool +cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *cdw) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + size_t copy_len, off, buffer_offset; + int ctl_sg_count; + union ctl_io *io; + + cs = PDU_SESSION(request); + + KASSERT((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT || + (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("bad opcode 0x%x", request->ip_bhs->bhs_opcode)); + + /* + * We're only using fields common for Data Out and SCSI Command PDUs. + */ + bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; + + io = cdw->cdw_ctl_io; + KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, + ("CTL_FLAG_DATA_IN")); + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "received %zd bytes out of %d", + request->ip_data_len, io->scsiio.kern_total_len); +#endif + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } +#if 0 + if (ctl_sg_count > 1) + CFISCSI_SESSION_DEBUG(cs, "ctl_sg_count = %d", ctl_sg_count); +#endif + + if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) + buffer_offset = ntohl(bhsdo->bhsdo_buffer_offset); + else + buffer_offset = 0; + + /* + * Make sure the offset, as sent by the initiator, matches the offset + * we're supposed to be at in the scatter-gather list. + */ + if (buffer_offset != io->scsiio.ext_data_filled) { + CFISCSI_SESSION_WARN(cs, "received bad buffer offset %zd, " + "expected %zd", buffer_offset, + (size_t)io->scsiio.ext_data_filled); + cfiscsi_session_terminate(cs); + return (true); + } + + off = 0; + for (;;) { + KASSERT(cdw->cdw_sg_index < ctl_sg_count, + ("cdw->cdw_sg_index >= ctl_sg_count")); + if (cdw->cdw_sg_len == 0) { + cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; + cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; + } + copy_len = icl_pdu_data_segment_length(request) - off; + if (copy_len > cdw->cdw_sg_len) + copy_len = cdw->cdw_sg_len; + + icl_pdu_get_data(request, off, cdw->cdw_sg_addr, copy_len); + cdw->cdw_sg_addr += copy_len; + cdw->cdw_sg_len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (cdw->cdw_sg_len == 0) { + if (cdw->cdw_sg_index == ctl_sg_count - 1) + break; + cdw->cdw_sg_index++; + } + if (off == icl_pdu_data_segment_length(request)) + break; + } + + if (off < icl_pdu_data_segment_length(request)) { + CFISCSI_SESSION_WARN(cs, "received too much data: got %zd bytes, " + "expected %zd", icl_pdu_data_segment_length(request), off); + cfiscsi_session_terminate(cs); + return (true); + } + + if (bhsdo->bhsdo_flags & BHSDO_FLAGS_F || + io->scsiio.ext_data_filled == io->scsiio.kern_total_len) { + if ((bhsdo->bhsdo_flags & BHSDO_FLAGS_F) == 0) { + CFISCSI_SESSION_WARN(cs, "got the final packet without " + "the F flag; flags = 0x%x; dropping connection", + bhsdo->bhsdo_flags); + cfiscsi_session_terminate(cs); + return (true); + } + + if (io->scsiio.ext_data_filled != io->scsiio.kern_total_len) { + if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { + CFISCSI_SESSION_WARN(cs, "got the final packet, but the " + "transmitted size was %zd bytes instead of %d; " + "dropping connection", + (size_t)io->scsiio.ext_data_filled, + io->scsiio.kern_total_len); + cfiscsi_session_terminate(cs); + return (true); + } else { + /* + * For SCSI Command PDU, this just means we need to + * solicit more data by sending R2T. + */ + return (false); + } + } +#if 0 + CFISCSI_SESSION_DEBUG(cs, "no longer expecting Data-Out with target " + "transfer tag 0x%x", cdw->cdw_target_transfer_tag); +#endif + + return (true); + } + + return (false); +} + +static void +cfiscsi_pdu_handle_data_out(struct icl_pdu *request) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct cfiscsi_data_wait *cdw = NULL; + union ctl_io *io; + bool done; + + cs = PDU_SESSION(request); + bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) { +#if 0 + CFISCSI_SESSION_DEBUG(cs, "have ttt 0x%x, itt 0x%x; looking for " + "ttt 0x%x, itt 0x%x", + bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag, + cdw->cdw_target_transfer_tag, cdw->cdw_initiator_task_tag)); +#endif + if (bhsdo->bhsdo_target_transfer_tag == + cdw->cdw_target_transfer_tag) + break; + } + CFISCSI_SESSION_UNLOCK(cs); + if (cdw == NULL) { + CFISCSI_SESSION_WARN(cs, "data transfer tag 0x%x, initiator task tag " + "0x%x, not found", bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag); + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + io = cdw->cdw_ctl_io; + KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, + ("CTL_FLAG_DATA_IN")); + + done = cfiscsi_handle_data_segment(request, cdw); + if (done) { + CFISCSI_SESSION_LOCK(cs); + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + uma_zfree(cfiscsi_data_wait_zone, cdw); + io->scsiio.be_move_done(io); + } + + icl_pdu_free(request); +} + +static void +cfiscsi_pdu_handle_logout_request(struct icl_pdu *request) +{ + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + struct icl_pdu *response; + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs; + switch (bhslr->bhslr_reason & 0x7f) { + case BHSLR_REASON_CLOSE_SESSION: + case BHSLR_REASON_CLOSE_CONNECTION: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + icl_pdu_free(request); + cfiscsi_pdu_queue(response); + cfiscsi_session_terminate(cs); + break; + case BHSLR_REASON_REMOVE_FOR_RECOVERY: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + icl_pdu_free(request); + cfiscsi_pdu_queue(response); + break; + default: + CFISCSI_SESSION_WARN(cs, "invalid reason 0%x; dropping connection", + bhslr->bhslr_reason); + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + break; + } +} + +static void +cfiscsi_callout(void *context) +{ + struct icl_pdu *cp; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_session *cs; + + cs = context; + + if (cs->cs_terminating) + return; + + callout_schedule(&cs->cs_callout, 1 * hz); + + CFISCSI_SESSION_LOCK(cs); + cs->cs_timeout++; + CFISCSI_SESSION_UNLOCK(cs); + +#ifdef ICL_KERNEL_PROXY + if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { + if (cs->cs_timeout > login_timeout) { + CFISCSI_SESSION_WARN(cs, "login timed out after " + "%d seconds; dropping connection", cs->cs_timeout); + cfiscsi_session_terminate(cs); + } + return; + } +#endif + + if (cs->cs_timeout >= ping_timeout) { + CFISCSI_SESSION_WARN(cs, "no ping reply (NOP-Out) after %d seconds; " + "dropping connection", ping_timeout); + cfiscsi_session_terminate(cs); + return; + } + + /* + * If the ping was reset less than one second ago - which means + * that we've received some PDU during the last second - assume + * the traffic flows correctly and don't bother sending a NOP-Out. + * + * (It's 2 - one for one second, and one for incrementing is_timeout + * earlier in this routine.) + */ + if (cs->cs_timeout < 2) + return; + + cp = icl_pdu_new_bhs(cs->cs_conn, M_WAITOK); + bhsni = (struct iscsi_bhs_nop_in *)cp->ip_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = 0xffffffff; + + cfiscsi_pdu_queue(cp); +} + +static void +cfiscsi_session_terminate_tasks(struct cfiscsi_session *cs) +{ + struct cfiscsi_data_wait *cdw, *tmpcdw; + union ctl_io *io; + int error; + +#ifdef notyet + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = lun; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK_SET; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + } +#else + /* + * CTL doesn't currently support CTL_TASK_ABORT_TASK_SET, so instead + * just iterate over tasks that are waiting for something - data - and + * terminate those. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = + cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + //io->io_hdr.nexus.targ_lun = lun; /* Not needed? */ + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK; + io->taskio.tag_num = cdw->cdw_initiator_task_tag; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + return; + } +#if 0 + CFISCSI_SESSION_DEBUG(cs, "removing csw for initiator task tag " + "0x%x", cdw->cdw_initiator_task_tag); +#endif + cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); + uma_zfree(cfiscsi_data_wait_zone, cdw); + } + CFISCSI_SESSION_UNLOCK(cs); +#endif +} + +static void +cfiscsi_maintenance_thread(void *arg) +{ + struct cfiscsi_session *cs; + + cs = arg; + + for (;;) { + CFISCSI_SESSION_LOCK(cs); + if (cs->cs_terminating == false) + cv_wait(&cs->cs_maintenance_cv, &cs->cs_lock); + CFISCSI_SESSION_UNLOCK(cs); + + if (cs->cs_terminating) { + cfiscsi_session_terminate_tasks(cs); + callout_drain(&cs->cs_callout); + + icl_conn_shutdown(cs->cs_conn); + icl_conn_close(cs->cs_conn); + + cs->cs_terminating++; + + /* + * XXX: We used to wait up to 30 seconds to deliver queued PDUs + * to the initiator. We also tried hard to deliver SCSI Responses + * for the aborted PDUs. We don't do that anymore. We might need + * to revisit that. + */ + + cfiscsi_session_delete(cs); + kthread_exit(); + return; + } + CFISCSI_SESSION_DEBUG(cs, "nothing to do"); + } +} + +static void +cfiscsi_session_terminate(struct cfiscsi_session *cs) +{ + + if (cs->cs_terminating != 0) + return; + cs->cs_terminating = 1; + cv_signal(&cs->cs_maintenance_cv); +} + +static int +cfiscsi_session_register_initiator(struct cfiscsi_session *cs) +{ + int error, i; + struct cfiscsi_softc *softc; + + KASSERT(cs->cs_ctl_initid == -1, ("already registered")); + + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + for (i = 0; i < softc->max_initiators; i++) { + if (softc->ctl_initids[i] == 0) + break; + } + if (i == softc->max_initiators) { + CFISCSI_SESSION_WARN(cs, "too many concurrent sessions (%d)", + softc->max_initiators); + mtx_unlock(&softc->lock); + return (1); + } + softc->ctl_initids[i] = 1; + mtx_unlock(&softc->lock); + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "adding initiator id %d, max %d", + i, softc->max_initiators); +#endif + cs->cs_ctl_initid = i; + error = ctl_add_initiator(0x0, softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "ctl_add_initiator failed with error %d", error); + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; + return (1); + } + + return (0); +} + +static void +cfiscsi_session_unregister_initiator(struct cfiscsi_session *cs) +{ + int error; + struct cfiscsi_softc *softc; + + if (cs->cs_ctl_initid == -1) + return; + + softc = &cfiscsi_softc; + + error = ctl_remove_initiator(softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "ctl_remove_initiator failed with error %d", + error); + } + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; +} + +static struct cfiscsi_session * +cfiscsi_session_new(struct cfiscsi_softc *softc) +{ + struct cfiscsi_session *cs; + int error; + + cs = malloc(sizeof(*cs), M_CFISCSI, M_NOWAIT | M_ZERO); + if (cs == NULL) { + CFISCSI_WARN("malloc failed"); + return (NULL); + } + cs->cs_ctl_initid = -1; + + refcount_init(&cs->cs_outstanding_ctl_pdus, 0); + TAILQ_INIT(&cs->cs_waiting_for_data_out); + mtx_init(&cs->cs_lock, "cfiscsi_lock", NULL, MTX_DEF); + cv_init(&cs->cs_maintenance_cv, "cfiscsi_mt"); +#ifdef ICL_KERNEL_PROXY + cv_init(&cs->cs_login_cv, "cfiscsi_login"); +#endif + + cs->cs_conn = icl_conn_new(); + cs->cs_conn->ic_receive = cfiscsi_receive_callback; + cs->cs_conn->ic_error = cfiscsi_error_callback; + cs->cs_conn->ic_prv0 = cs; + + error = kthread_add(cfiscsi_maintenance_thread, cs, NULL, NULL, 0, 0, "cfiscsimt"); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "kthread_add(9) failed with error %d", error); + free(cs, M_CFISCSI); + return (NULL); + } + + mtx_lock(&softc->lock); + cs->cs_id = softc->last_session_id + 1; + softc->last_session_id++; + mtx_unlock(&softc->lock); + + mtx_lock(&softc->lock); + TAILQ_INSERT_TAIL(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + /* + * Start pinging the initiator. + */ + callout_init(&cs->cs_callout, 1); + callout_reset(&cs->cs_callout, 1 * hz, cfiscsi_callout, cs); + + return (cs); +} + +static void +cfiscsi_session_delete(struct cfiscsi_session *cs) +{ + struct cfiscsi_softc *softc; + + softc = &cfiscsi_softc; + + KASSERT(cs->cs_outstanding_ctl_pdus == 0, + ("destroying session with outstanding CTL pdus")); + KASSERT(TAILQ_EMPTY(&cs->cs_waiting_for_data_out), + ("destroying session with non-empty queue")); + + cfiscsi_session_unregister_initiator(cs); + if (cs->cs_target != NULL) + cfiscsi_target_release(cs->cs_target); + icl_conn_close(cs->cs_conn); + icl_conn_free(cs->cs_conn); + + mtx_lock(&softc->lock); + TAILQ_REMOVE(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + free(cs, M_CFISCSI); +} + +int +cfiscsi_init(void) +{ + struct cfiscsi_softc *softc; + struct ctl_frontend *fe; + int retval; + + softc = &cfiscsi_softc; + retval = 0; + bzero(softc, sizeof(*softc)); + mtx_init(&softc->lock, "cfiscsi", NULL, MTX_DEF); + +#ifdef ICL_KERNEL_PROXY + cv_init(&softc->accept_cv, "cfiscsi_accept"); +#endif + TAILQ_INIT(&softc->sessions); + TAILQ_INIT(&softc->targets); + + fe = &softc->fe; + fe->port_type = CTL_PORT_ISCSI; + /* XXX KDM what should the real number be here? */ + fe->num_requested_ctl_io = 4096; + snprintf(softc->port_name, sizeof(softc->port_name), "iscsi"); + fe->port_name = softc->port_name; + fe->port_online = cfiscsi_online; + fe->port_offline = cfiscsi_offline; + fe->onoff_arg = softc; + fe->targ_enable = cfiscsi_targ_enable; + fe->targ_disable = cfiscsi_targ_disable; + fe->lun_enable = cfiscsi_lun_enable; + fe->lun_disable = cfiscsi_lun_disable; + fe->targ_lun_arg = softc; + fe->ioctl = cfiscsi_ioctl; + fe->devid = cfiscsi_devid; + fe->fe_datamove = cfiscsi_datamove; + fe->fe_done = cfiscsi_done; + + /* XXX KDM what should we report here? */ + /* XXX These should probably be fetched from CTL. */ + fe->max_targets = 1; + fe->max_target_id = 15; + + retval = ctl_frontend_register(fe, /*master_SC*/ 1); + if (retval != 0) { + CFISCSI_WARN("ctl_frontend_register() failed with error %d", + retval); + retval = 1; + goto bailout; + } + + softc->max_initiators = fe->max_initiators; + + cfiscsi_data_wait_zone = uma_zcreate("cfiscsi_data_wait", + sizeof(struct cfiscsi_data_wait), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + return (0); + +bailout: + return (retval); +} + +static int +cfiscsi_module_event_handler(module_t mod, int what, void *arg) +{ + + switch (what) { + case MOD_LOAD: + return (cfiscsi_init()); + case MOD_UNLOAD: + return (EBUSY); + default: + return (EOPNOTSUPP); + } +} + +#ifdef ICL_KERNEL_PROXY +static void +cfiscsi_accept(struct socket *so) +{ + struct cfiscsi_session *cs; + + cs = cfiscsi_session_new(&cfiscsi_softc); + if (cs == NULL) { + CFISCSI_WARN("failed to create session"); + return; + } + + icl_conn_handoff_sock(cs->cs_conn, so); + cs->cs_waiting_for_ctld = true; + cv_signal(&cfiscsi_softc.accept_cv); +} +#endif + +static void +cfiscsi_online(void *arg) +{ + struct cfiscsi_softc *softc; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 1; +#ifdef ICL_KERNEL_PROXY + if (softc->listener != NULL) + icl_listen_free(softc->listener); + softc->listener = icl_listen_new(cfiscsi_accept); +#endif +} + +static void +cfiscsi_offline(void *arg) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_session *cs; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 0; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) + cfiscsi_session_terminate(cs); + mtx_unlock(&softc->lock); + +#ifdef ICL_KERNEL_PROXY + icl_listen_free(softc->listener); + softc->listener = NULL; +#endif +} + +static int +cfiscsi_targ_enable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static int +cfiscsi_targ_disable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static void +cfiscsi_ioctl_handoff(struct ctl_iscsi *ci) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_session *cs; + struct cfiscsi_target *ct; + struct ctl_iscsi_handoff_params *cihp; +#ifndef ICL_KERNEL_PROXY + int error; +#endif + + cihp = (struct ctl_iscsi_handoff_params *)&(ci->data); + softc = &cfiscsi_softc; + + CFISCSI_DEBUG("new connection from %s (%s) to %s", + cihp->initiator_name, cihp->initiator_addr, + cihp->target_name); + + if (softc->online == 0) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: port offline", __func__); + return; + } + + ct = cfiscsi_target_find(softc, cihp->target_name); + if (ct == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: target not found", __func__); + return; + } + +#ifdef ICL_KERNEL_PROXY + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cihp->socket) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); +#else + cs = cfiscsi_session_new(softc); + if (cs == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: cfiscsi_session_new failed", __func__); + cfiscsi_target_release(ct); + return; + } +#endif + cs->cs_target = ct; + + /* + * First PDU of Full Feature phase has the same CmdSN as the last + * PDU from the Login Phase received from the initiator. Thus, + * the -1 below. + */ + cs->cs_portal_group_tag = cihp->portal_group_tag; + cs->cs_cmdsn = cihp->cmdsn; + cs->cs_statsn = cihp->statsn; + cs->cs_max_data_segment_length = cihp->max_recv_data_segment_length; + cs->cs_max_burst_length = cihp->max_burst_length; + cs->cs_immediate_data = !!cihp->immediate_data; + if (cihp->header_digest == CTL_ISCSI_DIGEST_CRC32C) + cs->cs_conn->ic_header_crc32c = true; + if (cihp->data_digest == CTL_ISCSI_DIGEST_CRC32C) + cs->cs_conn->ic_data_crc32c = true; + + strlcpy(cs->cs_initiator_name, + cihp->initiator_name, sizeof(cs->cs_initiator_name)); + strlcpy(cs->cs_initiator_addr, + cihp->initiator_addr, sizeof(cs->cs_initiator_addr)); + strlcpy(cs->cs_initiator_alias, + cihp->initiator_alias, sizeof(cs->cs_initiator_alias)); + +#ifdef ICL_KERNEL_PROXY + cs->cs_login_phase = false; +#else + error = icl_conn_handoff(cs->cs_conn, cihp->socket); + if (error != 0) { + cfiscsi_session_delete(cs); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: icl_conn_handoff failed with error %d", + __func__, error); + return; + } +#endif + + /* + * Register initiator with CTL. + */ + cfiscsi_session_register_initiator(cs); + +#ifdef ICL_KERNEL_PROXY + /* + * First PDU of the Full Feature phase has likely already arrived. + * We have to pick it up and execute properly. + */ + if (cs->cs_login_pdu != NULL) { + CFISCSI_SESSION_DEBUG(cs, "picking up first PDU"); + cfiscsi_pdu_handle(cs->cs_login_pdu); + cs->cs_login_pdu = NULL; + } +#endif + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_list(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_list_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + struct sbuf *sb; + int error; + + cilp = (struct ctl_iscsi_list_params *)&(ci->data); + softc = &cfiscsi_softc; + + sb = sbuf_new(NULL, NULL, cilp->alloc_len, SBUF_FIXEDLEN); + if (sb == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate %d bytes for iSCSI session list", + cilp->alloc_len); + return; + } + + sbuf_printf(sb, "\n"); + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { +#ifdef ICL_KERNEL_PROXY + if (cs->cs_target == NULL) + continue; +#endif + error = sbuf_printf(sb, "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%zd" + "%d" + "%d" + "\n", + cs->cs_id, + cs->cs_initiator_name, cs->cs_initiator_addr, cs->cs_initiator_alias, + cs->cs_target->ct_name, cs->cs_target->ct_alias, + cs->cs_conn->ic_header_crc32c ? "CRC32C" : "None", + cs->cs_conn->ic_data_crc32c ? "CRC32C" : "None", + cs->cs_max_data_segment_length, + cs->cs_immediate_data, + cs->cs_conn->ic_iser); + if (error != 0) + break; + } + mtx_unlock(&softc->lock); + error = sbuf_printf(sb, "\n"); + if (error != 0) { + sbuf_delete(sb); + ci->status = CTL_ISCSI_LIST_NEED_MORE_SPACE; + snprintf(ci->error_str, sizeof(ci->error_str), + "Out of space, %d bytes is too small", cilp->alloc_len); + return; + } + sbuf_finish(sb); + + error = copyout(sbuf_data(sb), cilp->conn_xml, sbuf_len(sb) + 1); + cilp->fill_len = sbuf_len(sb) + 1; + ci->status = CTL_ISCSI_OK; + sbuf_delete(sb); +} + +static void +cfiscsi_ioctl_terminate(struct ctl_iscsi *ci) +{ + struct icl_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_terminate_params *citp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + citp = (struct ctl_iscsi_terminate_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (citp->all == 0 && cs->cs_id != citp->connection_id && + strcmp(cs->cs_initiator_name, citp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, citp->initiator_addr) != 0) + continue; + + response = icl_pdu_new_bhs(cs->cs_conn, M_NOWAIT); + if (response == NULL) { + /* + * Oh well. Just terminate the connection. + */ + } else { + bhsam = (struct iscsi_bhs_asynchronous_message *) + response->ip_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_0xffffffff = 0xffffffff; + bhsam->bhsam_async_event = + BHSAM_EVENT_TARGET_TERMINATES_SESSION; + cfiscsi_pdu_queue(response); + } + cfiscsi_session_terminate(cs); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_logout(struct ctl_iscsi *ci) +{ + struct icl_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_logout_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + cilp = (struct ctl_iscsi_logout_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (cilp->all == 0 && cs->cs_id != cilp->connection_id && + strcmp(cs->cs_initiator_name, cilp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, cilp->initiator_addr) != 0) + continue; + + response = icl_pdu_new_bhs(cs->cs_conn, M_NOWAIT); + if (response == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate memory"); + mtx_unlock(&softc->lock); + return; + } + bhsam = + (struct iscsi_bhs_asynchronous_message *)response->ip_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_async_event = BHSAM_EVENT_TARGET_REQUESTS_LOGOUT; + bhsam->bhsam_parameter3 = htons(10); + cfiscsi_pdu_queue(response); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +#ifdef ICL_KERNEL_PROXY +static void +cfiscsi_ioctl_listen(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_listen_params *cilp; + struct sockaddr *sa; + int error; + + cilp = (struct ctl_iscsi_listen_params *)&(ci->data); + + if (cfiscsi_softc.listener == NULL) { + CFISCSI_DEBUG("no listener"); + snprintf(ci->error_str, sizeof(ci->error_str), "no listener"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + error = getsockaddr(&sa, (void *)cilp->addr, cilp->addrlen); + if (error != 0) { + CFISCSI_DEBUG("getsockaddr, error %d", error); + snprintf(ci->error_str, sizeof(ci->error_str), "getsockaddr failed"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + error = icl_listen_add(cfiscsi_softc.listener, cilp->iser, cilp->domain, + cilp->socktype, cilp->protocol, sa); + if (error != 0) { + free(sa, M_SONAME); + CFISCSI_DEBUG("icl_listen_add, error %d", error); + snprintf(ci->error_str, sizeof(ci->error_str), + "icl_listen_add failed, error %d", error); + ci->status = CTL_ISCSI_ERROR; + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_accept(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_accept_params *ciap; + struct cfiscsi_session *cs; + int error; + + ciap = (struct ctl_iscsi_accept_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + for (;;) { + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_waiting_for_ctld) + break; + } + if (cs != NULL) + break; + error = cv_wait_sig(&cfiscsi_softc.accept_cv, &cfiscsi_softc.lock); + if (error != 0) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "interrupted"); + ci->status = CTL_ISCSI_ERROR; + return; + } + } + mtx_unlock(&cfiscsi_softc.lock); + + cs->cs_waiting_for_ctld = false; + cs->cs_login_phase = true; + + ciap->connection_id = cs->cs_id; + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_send(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_send_params *cisp; + struct cfiscsi_session *cs; + struct icl_pdu *ip; + size_t datalen; + void *data; + int error; + + cisp = (struct ctl_iscsi_send_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cisp->connection_id) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); + +#if 0 + if (cs->cs_login_phase == false) + return (EBUSY); +#endif + + if (cs->cs_terminating) { + snprintf(ci->error_str, sizeof(ci->error_str), "connection is terminating"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + datalen = cisp->data_segment_len; + /* + * XXX + */ + //if (datalen > CFISCSI_MAX_DATA_SEGMENT_LENGTH) { + if (datalen > 65535) { + snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); + ci->status = CTL_ISCSI_ERROR; + return; + } + if (datalen > 0) { + data = malloc(datalen, M_CFISCSI, M_WAITOK); + error = copyin(cisp->data_segment, data, datalen); + if (error != 0) { + free(data, M_CFISCSI); + snprintf(ci->error_str, sizeof(ci->error_str), "copyin error %d", error); + ci->status = CTL_ISCSI_ERROR; + return; + } + } + + ip = icl_pdu_new_bhs(cs->cs_conn, M_WAITOK); + memcpy(ip->ip_bhs, cisp->bhs, sizeof(*ip->ip_bhs)); + if (datalen > 0) { + icl_pdu_append_data(ip, data, datalen, M_WAITOK); + free(data, M_CFISCSI); + } + icl_pdu_queue(ip); + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_receive(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_receive_params *cirp; + struct cfiscsi_session *cs; + struct icl_pdu *ip; + void *data; + + cirp = (struct ctl_iscsi_receive_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cirp->connection_id) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); + +#if 0 + if (is->is_login_phase == false) + return (EBUSY); +#endif + + CFISCSI_SESSION_LOCK(cs); + while (cs->cs_login_pdu == NULL && + cs->cs_terminating == false) + cv_wait(&cs->cs_login_cv, &cs->cs_lock); + if (cs->cs_terminating) { + CFISCSI_SESSION_UNLOCK(cs); + snprintf(ci->error_str, sizeof(ci->error_str), "connection terminating"); + ci->status = CTL_ISCSI_ERROR; + return; + } + ip = cs->cs_login_pdu; + cs->cs_login_pdu = NULL; + CFISCSI_SESSION_UNLOCK(cs); + + if (ip->ip_data_len > cirp->data_segment_len) { + icl_pdu_free(ip); + snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + copyout(ip->ip_bhs, cirp->bhs, sizeof(*ip->ip_bhs)); + if (ip->ip_data_len > 0) { + data = malloc(ip->ip_data_len, M_CFISCSI, M_WAITOK); + icl_pdu_get_data(ip, 0, data, ip->ip_data_len); + copyout(data, cirp->data_segment, ip->ip_data_len); + free(data, M_CFISCSI); + } + + icl_pdu_free(ip); + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_close(struct ctl_iscsi *ci) +{ + /* + * XXX + */ +} +#endif /* !ICL_KERNEL_PROXY */ + +static int +cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct ctl_iscsi *ci; + + if (cmd != CTL_ISCSI) + return (ENOTTY); + + ci = (struct ctl_iscsi *)addr; + switch (ci->type) { + case CTL_ISCSI_HANDOFF: + cfiscsi_ioctl_handoff(ci); + break; + case CTL_ISCSI_LIST: + cfiscsi_ioctl_list(ci); + break; + case CTL_ISCSI_TERMINATE: + cfiscsi_ioctl_terminate(ci); + break; + case CTL_ISCSI_LOGOUT: + cfiscsi_ioctl_logout(ci); + break; +#ifdef ICL_KERNEL_PROXY + case CTL_ISCSI_LISTEN: + cfiscsi_ioctl_listen(ci); + break; + case CTL_ISCSI_ACCEPT: + cfiscsi_ioctl_accept(ci); + break; + case CTL_ISCSI_SEND: + cfiscsi_ioctl_send(ci); + break; + case CTL_ISCSI_RECEIVE: + cfiscsi_ioctl_receive(ci); + break; + case CTL_ISCSI_CLOSE: + cfiscsi_ioctl_close(ci); + break; +#endif /* ICL_KERNEL_PROXY */ + default: + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: invalid iSCSI request type %d", __func__, ci->type); + break; + } + + return (0); +} + +static int +cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len) +{ + struct cfiscsi_session *cs; + struct scsi_vpd_device_id *devid_ptr; + struct scsi_vpd_id_descriptor *desc, *desc1; + struct scsi_vpd_id_descriptor *desc2, *desc3; /* for types 4h and 5h */ + struct scsi_vpd_id_t10 *t10id; + struct ctl_lun *lun; + const struct icl_pdu *request; + size_t devid_len, wwpn_len; + + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; + request = ctsio->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = PDU_SESSION(request); + + wwpn_len = strlen(cs->cs_target->ct_name); + wwpn_len += strlen(",t,0x01"); + wwpn_len += 1; /* '\0' */ + if ((wwpn_len % 4) != 0) + wwpn_len += (4 - (wwpn_len % 4)); + + devid_len = sizeof(struct scsi_vpd_device_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN + + sizeof(struct scsi_vpd_id_descriptor) + wwpn_len + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_rel_trgt_port_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_trgt_port_grp_id); + + ctsio->kern_data_ptr = malloc(devid_len, M_CTL, M_WAITOK | M_ZERO); + devid_ptr = (struct scsi_vpd_device_id *)ctsio->kern_data_ptr; + ctsio->kern_sg_entries = 0; + + if (devid_len < alloc_len) { + ctsio->residual = alloc_len - devid_len; + ctsio->kern_data_len = devid_len; + ctsio->kern_total_len = devid_len; + } else { + ctsio->residual = 0; + ctsio->kern_data_len = alloc_len; + ctsio->kern_total_len = alloc_len; + } + ctsio->kern_data_resid = 0; + ctsio->kern_rel_offset = 0; + ctsio->kern_sg_entries = 0; + + desc = (struct scsi_vpd_id_descriptor *)devid_ptr->desc_list; + t10id = (struct scsi_vpd_id_t10 *)&desc->identifier[0]; + desc1 = (struct scsi_vpd_id_descriptor *)(&desc->identifier[0] + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN); + desc2 = (struct scsi_vpd_id_descriptor *)(&desc1->identifier[0] + + wwpn_len); + desc3 = (struct scsi_vpd_id_descriptor *)(&desc2->identifier[0] + + sizeof(struct scsi_vpd_id_rel_trgt_port_id)); + + if (lun != NULL) + devid_ptr->device = (SID_QUAL_LU_CONNECTED << 5) | + lun->be_lun->lun_type; + else + devid_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT; + + devid_ptr->page_code = SVPD_DEVICE_ID; + + scsi_ulto2b(devid_len - 4, devid_ptr->length); + + /* + * We're using a LUN association here. i.e., this device ID is a + * per-LUN identifier. + */ + desc->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_ASCII; + desc->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_LUN | SVPD_ID_TYPE_T10; + desc->length = sizeof(*t10id) + CTL_DEVID_LEN; + strncpy((char *)t10id->vendor, CTL_VENDOR, sizeof(t10id->vendor)); + + /* + * If we've actually got a backend, copy the device id from the + * per-LUN data. Otherwise, set it to all spaces. + */ + if (lun != NULL) { + /* + * Copy the backend's LUN ID. + */ + strncpy((char *)t10id->vendor_spec_id, + (char *)lun->be_lun->device_id, CTL_DEVID_LEN); + } else { + /* + * No backend, set this to spaces. + */ + memset(t10id->vendor_spec_id, 0x20, CTL_DEVID_LEN); + } + + /* + * desc1 is for the WWPN which is a port asscociation. + */ + desc1->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_UTF8; + desc1->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_SCSI_NAME; + desc1->length = wwpn_len; + snprintf(desc1->identifier, wwpn_len, "%s,t,0x%x", + cs->cs_target->ct_name, cs->cs_portal_group_tag); + + /* + * desc2 is for the Relative Target Port(type 4h) identifier + */ + desc2->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc2->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_RELTARG; + desc2->length = 4; + desc2->identifier[3] = 1; + + /* + * desc3 is for the Target Port Group(type 5h) identifier + */ + desc3->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc3->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_TPORTGRP; + desc3->length = 4; + desc3->identifier[3] = 1; + + ctsio->scsi_status = SCSI_STATUS_OK; + + ctsio->be_move_done = ctl_config_move_done; + ctl_datamove((union ctl_io *)ctsio); + + return (CTL_RETVAL_COMPLETE); +} + +static void +cfiscsi_target_hold(struct cfiscsi_target *ct) +{ + + refcount_acquire(&ct->ct_refcount); +} + +static void +cfiscsi_target_release(struct cfiscsi_target *ct) +{ + int old; + struct cfiscsi_softc *softc; + + softc = ct->ct_softc; + + old = ct->ct_refcount; + if (old > 1 && atomic_cmpset_int(&ct->ct_refcount, old, old - 1)) + return; + + mtx_lock(&softc->lock); + if (refcount_release(&ct->ct_refcount)) { + TAILQ_REMOVE(&softc->targets, ct, ct_next); + mtx_unlock(&softc->lock); + free(ct, M_CFISCSI); + + return; + } + mtx_unlock(&softc->lock); +} + +static struct cfiscsi_target * +cfiscsi_target_find(struct cfiscsi_softc *softc, const char *name) +{ + struct cfiscsi_target *ct; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + if (strcmp(name, ct->ct_name) != 0) + continue; + cfiscsi_target_hold(ct); + mtx_unlock(&softc->lock); + return (ct); + } + mtx_unlock(&softc->lock); + + return (NULL); +} + +static struct cfiscsi_target * +cfiscsi_target_find_or_create(struct cfiscsi_softc *softc, const char *name, + const char *alias) +{ + struct cfiscsi_target *ct, *newct; + int i; + + if (name[0] == '\0' || strlen(name) >= CTL_ISCSI_NAME_LEN) + return (NULL); + + newct = malloc(sizeof(*newct), M_CFISCSI, M_WAITOK | M_ZERO); + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + if (strcmp(name, ct->ct_name) != 0) + continue; + cfiscsi_target_hold(ct); + mtx_unlock(&softc->lock); + free(newct, M_CFISCSI); + return (ct); + } + + for (i = 0; i < CTL_MAX_LUNS; i++) + newct->ct_luns[i] = -1; + + strlcpy(newct->ct_name, name, sizeof(newct->ct_name)); + if (alias != NULL) + strlcpy(newct->ct_alias, alias, sizeof(newct->ct_alias)); + refcount_init(&newct->ct_refcount, 1); + newct->ct_softc = softc; + TAILQ_INSERT_TAIL(&softc->targets, newct, ct_next); + mtx_unlock(&softc->lock); + + return (newct); +} + +/* + * Takes LUN from the target space and returns LUN from the CTL space. + */ +static uint32_t +cfiscsi_map_lun(void *arg, uint32_t lun) +{ + struct cfiscsi_session *cs; + + cs = arg; + + if (lun >= CTL_MAX_LUNS) { + CFISCSI_DEBUG("requested lun number %d is higher " + "than maximum %d", lun, CTL_MAX_LUNS - 1); + return (0xffffffff); + } + + if (cs->cs_target->ct_luns[lun] < 0) + return (0xffffffff); + + return (cs->cs_target->ct_luns[lun]); +} + +static int +cfiscsi_target_set_lun(struct cfiscsi_target *ct, + unsigned long lun_id, unsigned long ctl_lun_id) +{ + + if (lun_id >= CTL_MAX_LUNS) { + CFISCSI_WARN("requested lun number %ld is higher " + "than maximum %d", lun_id, CTL_MAX_LUNS - 1); + return (-1); + } + + if (ct->ct_luns[lun_id] >= 0) { + /* + * CTL calls cfiscsi_lun_enable() twice for each LUN - once + * when the LUN is created, and a second time just before + * the port is brought online; don't emit warnings + * for that case. + */ + if (ct->ct_luns[lun_id] == ctl_lun_id) + return (0); + CFISCSI_WARN("lun %ld already allocated", lun_id); + return (-1); + } + +#if 0 + CFISCSI_DEBUG("adding mapping for lun %ld, target %s " + "to ctl lun %ld", lun_id, ct->ct_name, ctl_lun_id); +#endif + + ct->ct_luns[lun_id] = ctl_lun_id; + cfiscsi_target_hold(ct); + + return (0); +} + +static int +cfiscsi_target_unset_lun(struct cfiscsi_target *ct, unsigned long lun_id) +{ + + if (ct->ct_luns[lun_id] < 0) { + CFISCSI_WARN("lun %ld not allocated", lun_id); + return (-1); + } + + ct->ct_luns[lun_id] = -1; + cfiscsi_target_release(ct); + + return (0); +} + +static int +cfiscsi_lun_enable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + struct ctl_be_lun_option *opt; + const char *target = NULL, *target_alias = NULL; + const char *lun = NULL; + unsigned long tmp; + + softc = (struct cfiscsi_softc *)arg; + + STAILQ_FOREACH(opt, + &control_softc->ctl_luns[lun_id]->be_lun->options, links) { + if (strcmp(opt->name, "cfiscsi_target") == 0) + target = opt->value; + else if (strcmp(opt->name, "cfiscsi_target_alias") == 0) + target_alias = opt->value; + else if (strcmp(opt->name, "cfiscsi_lun") == 0) + lun = opt->value; + } + + if (target == NULL && lun == NULL) + return (0); + + if (target == NULL || lun == NULL) { + CFISCSI_WARN("lun added with cfiscsi_target, but without " + "cfiscsi_lun, or the other way around; ignoring"); + return (0); + } + + ct = cfiscsi_target_find_or_create(softc, target, target_alias); + if (ct == NULL) { + CFISCSI_WARN("failed to create target \"%s\"", target); + return (0); + } + + tmp = strtoul(lun, NULL, 10); + cfiscsi_target_set_lun(ct, tmp, lun_id); + return (0); +} + +static int +cfiscsi_lun_disable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + int i; + + softc = (struct cfiscsi_softc *)arg; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + for (i = 0; i < CTL_MAX_LUNS; i++) { + if (ct->ct_luns[i] < 0) + continue; + if (ct->ct_luns[i] != lun_id) + continue; + cfiscsi_target_unset_lun(ct, i); + break; + } + } + mtx_unlock(&softc->lock); + return (0); +} + +static void +cfiscsi_datamove(union ctl_io *io) +{ + struct cfiscsi_session *cs; + struct icl_pdu *request, *response; + const struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_data_in *bhsdi; + struct iscsi_bhs_r2t *bhsr2t; + struct cfiscsi_data_wait *cdw; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + size_t copy_len, len, off; + const char *addr; + int ctl_sg_count, i; + uint32_t target_transfer_tag; + bool done; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = PDU_SESSION(request); + + bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_COMMAND")); + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } + + /* + * We need to record it so that we can properly report + * underflow/underflow. + */ + PDU_TOTAL_TRANSFER_LEN(request) = io->scsiio.kern_total_len; + + if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { +#if 0 + if (ctl_sg_count > 1) + CFISCSI_SESSION_DEBUG(cs, "ctl_sg_count = %d", ctl_sg_count); +#endif + + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + */ + off = htonl(io->scsiio.kern_rel_offset); + if (off > 1) + CFISCSI_SESSION_DEBUG(cs, "off = %zd", off); + + i = 0; + addr = NULL; + len = 0; + response = NULL; + bhsdi = NULL; + for (;;) { + KASSERT(i < ctl_sg_count, ("i >= ctl_sg_count")); + if (response == NULL) { + response = + cfiscsi_pdu_new_response(request, M_WAITOK); + bhsdi = (struct iscsi_bhs_data_in *) + response->ip_bhs; + bhsdi->bhsdi_opcode = + ISCSI_BHS_OPCODE_SCSI_DATA_IN; + bhsdi->bhsdi_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsdi->bhsdi_datasn = + htonl(PDU_EXPDATASN(request)); + PDU_EXPDATASN(request)++; + bhsdi->bhsdi_buffer_offset = htonl(off); + } + + if (len == 0) { + addr = ctl_sglist[i].addr; + len = ctl_sglist[i].len; + KASSERT(len > 0, ("len <= 0")); + } + + copy_len = len; + if (response->ip_data_len + copy_len > + cs->cs_max_data_segment_length) + copy_len = cs->cs_max_data_segment_length - + response->ip_data_len; + KASSERT(copy_len <= len, ("copy_len > len")); + icl_pdu_append_data(response, addr, copy_len, M_WAITOK); + addr += copy_len; + len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (len == 0) { + /* + * End of scatter-gather segment; + * proceed to the next one... + */ + if (i == ctl_sg_count - 1) { + /* + * ... unless this was the last one. + */ + break; + } + i++; + } + + if (response->ip_data_len == + cs->cs_max_data_segment_length) { + /* + * Can't stuff more data into the current PDU; + * queue it. Note that's not enough to check + * for kern_data_resid == 0 instead; there + * may be several Data-In PDUs for the final + * call to cfiscsi_datamove(), and we want + * to set the F flag only on the last of them. + */ + if (off == io->scsiio.kern_total_len) + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + KASSERT(response->ip_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + response = NULL; + bhsdi = NULL; + } + } + KASSERT(i == ctl_sg_count - 1, ("missed SG segment")); + KASSERT(len == 0, ("missed data from SG segment")); + if (response != NULL) { + if (off == io->scsiio.kern_total_len) { + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + } else { + CFISCSI_SESSION_DEBUG(cs, "not setting the F flag; " + "have %zd, need %zd", off, + (size_t)io->scsiio.kern_total_len); + } + KASSERT(response->ip_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + } + + io->scsiio.be_move_done(io); + } else { + CFISCSI_SESSION_LOCK(cs); + target_transfer_tag = cs->cs_target_transfer_tag; + cs->cs_target_transfer_tag++; + CFISCSI_SESSION_UNLOCK(cs); + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "expecting Data-Out with initiator " + "task tag 0x%x, target transfer tag 0x%x", + bhssc->bhssc_initiator_task_tag, target_transfer_tag); +#endif + cdw = uma_zalloc(cfiscsi_data_wait_zone, M_WAITOK | M_ZERO); + cdw->cdw_ctl_io = io; + cdw->cdw_target_transfer_tag = htonl(target_transfer_tag); + cdw->cdw_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + + if (cs->cs_immediate_data && + icl_pdu_data_segment_length(request) > 0) { + done = cfiscsi_handle_data_segment(request, cdw); + if (done) { + uma_zfree(cfiscsi_data_wait_zone, cdw); + io->scsiio.be_move_done(io); + return; + } + +#if 0 + if (io->scsiio.ext_data_filled != 0) + CFISCSI_SESSION_DEBUG(cs, "got %zd bytes of immediate data, need %zd", + io->scsiio.ext_data_filled, io->scsiio.kern_data_len); +#endif + } + + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_TAIL(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + + /* + * XXX: We should limit the number of outstanding R2T PDUs + * per task to MaxOutstandingR2T. + */ + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs; + bhsr2t->bhsr2t_opcode = ISCSI_BHS_OPCODE_R2T; + bhsr2t->bhsr2t_flags = 0x80; + bhsr2t->bhsr2t_lun = bhssc->bhssc_lun; + bhsr2t->bhsr2t_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsr2t->bhsr2t_target_transfer_tag = + htonl(target_transfer_tag); + /* + * XXX: Here we assume that cfiscsi_datamove() won't ever + * be running concurrently on several CPUs for a given + * command. + */ + bhsr2t->bhsr2t_r2tsn = htonl(PDU_R2TSN(request)); + PDU_R2TSN(request)++; + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + * + * The ext_data_filled is to account for unsolicited + * (immediate) data that might have already arrived. + */ + bhsr2t->bhsr2t_buffer_offset = + htonl(io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled); + /* + * This is the total length (sum of S/G lengths) this call + * to cfiscsi_datamove() is supposed to handle. + * + * XXX: Limit it to MaxBurstLength. + */ + bhsr2t->bhsr2t_desired_data_transfer_length = + htonl(io->scsiio.kern_data_len - io->scsiio.ext_data_filled); + cfiscsi_pdu_queue(response); + } +} + +static void +cfiscsi_scsi_command_done(union ctl_io *io) +{ + struct icl_pdu *request, *response; + struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_scsi_response *bhssr; +#ifdef DIAGNOSTIC + struct cfiscsi_data_wait *cdw; +#endif + struct cfiscsi_session *cs; + uint16_t sense_length; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = PDU_SESSION(request); + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("replying to wrong opcode 0x%x", bhssc->bhssc_opcode)); + + //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", + // bhssc->bhssc_initiator_task_tag); + +#ifdef DIAGNOSTIC + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) + KASSERT(bhssc->bhssc_initiator_task_tag != + cdw->cdw_initiator_task_tag, ("dangling cdw")); + CFISCSI_SESSION_UNLOCK(cs); +#endif + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; + bhssr->bhssr_opcode = ISCSI_BHS_OPCODE_SCSI_RESPONSE; + bhssr->bhssr_flags = 0x80; + /* + * XXX: We don't deal with bidirectional under/overflows; + * does anything actually support those? + */ + if (PDU_TOTAL_TRANSFER_LEN(request) < + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_UNDERFLOW; + bhssr->bhssr_residual_count = + htonl(ntohl(bhssc->bhssc_expected_data_transfer_length) - + PDU_TOTAL_TRANSFER_LEN(request)); + //CFISCSI_SESSION_DEBUG(cs, "underflow; residual count %d", + // ntohl(bhssr->bhssr_residual_count)); + } else if (PDU_TOTAL_TRANSFER_LEN(request) > + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_OVERFLOW; + bhssr->bhssr_residual_count = + htonl(PDU_TOTAL_TRANSFER_LEN(request) - + ntohl(bhssc->bhssc_expected_data_transfer_length)); + //CFISCSI_SESSION_DEBUG(cs, "overflow; residual count %d", + // ntohl(bhssr->bhssr_residual_count)); + } + bhssr->bhssr_response = BHSSR_RESPONSE_COMMAND_COMPLETED; + bhssr->bhssr_status = io->scsiio.scsi_status; + bhssr->bhssr_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + bhssr->bhssr_expdatasn = htonl(PDU_EXPDATASN(request)); + + if (io->scsiio.sense_len > 0) { +#if 0 + CFISCSI_SESSION_DEBUG(cs, "returning %d bytes of sense data", + io->scsiio.sense_len); +#endif + sense_length = htons(io->scsiio.sense_len); + icl_pdu_append_data(response, + &sense_length, sizeof(sense_length), M_WAITOK); + icl_pdu_append_data(response, + &io->scsiio.sense_data, io->scsiio.sense_len, M_WAITOK); + } + + ctl_free_io(io); + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_task_management_done(union ctl_io *io) +{ + struct icl_pdu *request, *response; + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct cfiscsi_data_wait *cdw, *tmpcdw; + struct cfiscsi_session *cs; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = PDU_SESSION(request); + bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; + KASSERT((bhstmr->bhstmr_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_TASK_REQUEST, + ("replying to wrong opcode 0x%x", bhstmr->bhstmr_opcode)); + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x; referenced task tag 0x%x", + bhstmr->bhstmr_initiator_task_tag, + bhstmr->bhstmr_referenced_task_tag); +#endif + + if ((bhstmr->bhstmr_function & ~0x80) == + BHSTMR_FUNCTION_ABORT_TASK) { + /* + * Make sure we no longer wait for Data-Out for this command. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + if (bhstmr->bhstmr_referenced_task_tag != + cdw->cdw_initiator_task_tag) + continue; + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "removing csw for initiator task " + "tag 0x%x", bhstmr->bhstmr_initiator_task_tag); +#endif + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, + cdw, cdw_next); + cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); + uma_zfree(cfiscsi_data_wait_zone, cdw); + } + CFISCSI_SESSION_UNLOCK(cs); + } + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->ip_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + if (io->io_hdr.status == CTL_SUCCESS) { + bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_COMPLETE; + } else { + /* + * XXX: How to figure out what exactly went wrong? iSCSI spec + * expects us to provide detailed error, e.g. "Task does + * not exist" or "LUN does not exist". + */ + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED"); + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + } + bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; + + ctl_free_io(io); + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_done(union ctl_io *io) +{ + struct icl_pdu *request; + struct cfiscsi_session *cs; + + KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), + ("invalid CTL status %#x", io->io_hdr.status)); + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + if (request == NULL) { + /* + * Implicit task termination has just completed; nothing to do. + */ + return; + } + + cs = PDU_SESSION(request); + refcount_release(&cs->cs_outstanding_ctl_pdus); + + switch (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_scsi_command_done(io); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_task_management_done(io); + break; + default: + panic("cfiscsi_done called with wrong opcode 0x%x", + request->ip_bhs->bhs_opcode); + } +} diff --git a/sys/cam/ctl/ctl_frontend_iscsi.h b/sys/cam/ctl/ctl_frontend_iscsi.h new file mode 100644 index 0000000..1642944 --- /dev/null +++ b/sys/cam/ctl/ctl_frontend_iscsi.h @@ -0,0 +1,112 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef CTL_FRONTEND_ISCSI_H +#define CTL_FRONTEND_ISCSI_H + +struct cfiscsi_target { + TAILQ_ENTRY(cfiscsi_target) ct_next; + int ct_luns[CTL_MAX_LUNS]; + struct cfiscsi_softc *ct_softc; + volatile u_int ct_refcount; + char ct_name[CTL_ISCSI_NAME_LEN]; + char ct_alias[CTL_ISCSI_ALIAS_LEN]; +}; + +struct cfiscsi_data_wait { + TAILQ_ENTRY(cfiscsi_data_wait) cdw_next; + union ctl_io *cdw_ctl_io; + uint32_t cdw_target_transfer_tag; + uint32_t cdw_initiator_task_tag; + int cdw_sg_index; + char *cdw_sg_addr; + size_t cdw_sg_len; +}; + +#define CFISCSI_SESSION_STATE_INVALID 0 +#define CFISCSI_SESSION_STATE_BHS 1 +#define CFISCSI_SESSION_STATE_AHS 2 +#define CFISCSI_SESSION_STATE_HEADER_DIGEST 3 +#define CFISCSI_SESSION_STATE_DATA 4 +#define CFISCSI_SESSION_STATE_DATA_DIGEST 5 + +struct cfiscsi_session { + TAILQ_ENTRY(cfiscsi_session) cs_next; + struct mtx cs_lock; + struct icl_conn *cs_conn; + uint32_t cs_cmdsn; + uint32_t cs_statsn; + uint32_t cs_target_transfer_tag; + volatile u_int cs_outstanding_ctl_pdus; + TAILQ_HEAD(, cfiscsi_data_wait) cs_waiting_for_data_out; + struct cfiscsi_target *cs_target; + struct callout cs_callout; + int cs_timeout; + int cs_portal_group_tag; + struct cv cs_maintenance_cv; + int cs_terminating; + size_t cs_max_data_segment_length; + size_t cs_max_burst_length; + bool cs_immediate_data; + char cs_initiator_name[CTL_ISCSI_NAME_LEN]; + char cs_initiator_addr[CTL_ISCSI_ADDR_LEN]; + char cs_initiator_alias[CTL_ISCSI_ALIAS_LEN]; + unsigned int cs_id; + int cs_ctl_initid; +#ifdef ICL_KERNEL_PROXY + bool cs_login_phase; + bool cs_waiting_for_ctld; + struct cv cs_login_cv; + struct icl_pdu *cs_login_pdu; +#endif +}; + +#ifdef ICL_KERNEL_PROXY +struct icl_listen; +#endif + +struct cfiscsi_softc { + struct ctl_frontend fe; + struct mtx lock; + char port_name[32]; + int online; + unsigned int last_session_id; + TAILQ_HEAD(, cfiscsi_target) targets; + TAILQ_HEAD(, cfiscsi_session) sessions; + char ctl_initids[CTL_MAX_INIT_PER_PORT]; + int max_initiators; +#ifdef ICL_KERNEL_PROXY + struct icl_listen *listener; + struct cv accept_cv; +#endif +}; + +#endif /* !CTL_FRONTEND_ISCSI_H */ diff --git a/sys/cam/ctl/ctl_ioctl.h b/sys/cam/ctl/ctl_ioctl.h index bc6342a..e9b0181 100644 --- a/sys/cam/ctl/ctl_ioctl.h +++ b/sys/cam/ctl/ctl_ioctl.h @@ -40,6 +40,12 @@ #ifndef _CTL_IOCTL_H_ #define _CTL_IOCTL_H_ +#ifdef ICL_KERNEL_PROXY +#include +#endif + +#include + #define CTL_DEFAULT_DEV "/dev/cam/ctl" /* * Maximum number of targets we support. @@ -588,6 +594,168 @@ struct ctl_lun_list { /* passed to userland */ }; +/* + * iSCSI status + * + * OK: Request completed successfully. + * + * ERROR: An error occured, look at the error string for a + * description of the error. + * + * CTL_ISCSI_LIST_NEED_MORE_SPACE: + * User has to pass larger buffer for CTL_ISCSI_LIST ioctl. + */ +typedef enum { + CTL_ISCSI_OK, + CTL_ISCSI_ERROR, + CTL_ISCSI_LIST_NEED_MORE_SPACE, + CTL_ISCSI_SESSION_NOT_FOUND +} ctl_iscsi_status; + +typedef enum { + CTL_ISCSI_HANDOFF, + CTL_ISCSI_LIST, + CTL_ISCSI_LOGOUT, + CTL_ISCSI_TERMINATE, +#ifdef ICL_KERNEL_PROXY + CTL_ISCSI_LISTEN, + CTL_ISCSI_ACCEPT, + CTL_ISCSI_SEND, + CTL_ISCSI_RECEIVE, + CTL_ISCSI_CLOSE, +#endif +} ctl_iscsi_type; + +typedef enum { + CTL_ISCSI_DIGEST_NONE, + CTL_ISCSI_DIGEST_CRC32C +} ctl_iscsi_digest; + +#define CTL_ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ +#define CTL_ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ +#define CTL_ISCSI_ALIAS_LEN 128 /* Arbitrary. */ + +struct ctl_iscsi_handoff_params { + char initiator_name[CTL_ISCSI_NAME_LEN]; + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + char initiator_alias[CTL_ISCSI_ALIAS_LEN]; + char target_name[CTL_ISCSI_NAME_LEN]; +#ifdef ICL_KERNEL_PROXY + int connection_id; + /* + * XXX + */ + int socket; +#else + int socket; +#endif + int portal_group_tag; + + /* + * Connection parameters negotiated by ctld(8). + */ + ctl_iscsi_digest header_digest; + ctl_iscsi_digest data_digest; + uint32_t cmdsn; + uint32_t statsn; + uint32_t max_recv_data_segment_length; + uint32_t max_burst_length; + uint32_t first_burst_length; + uint32_t immediate_data; +}; + +struct ctl_iscsi_list_params { + uint32_t alloc_len; /* passed to kernel */ + char *conn_xml; /* filled in kernel */ + uint32_t fill_len; /* passed to userland */ +}; + +struct ctl_iscsi_logout_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +struct ctl_iscsi_terminate_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +#ifdef ICL_KERNEL_PROXY +struct ctl_iscsi_listen_params { + int iser; + int domain; + int socktype; + int protocol; + struct sockaddr *addr; + socklen_t addrlen; +}; + +struct ctl_iscsi_accept_params { + int connection_id; +}; + +struct ctl_iscsi_send_params { + int connection_id; + void *bhs; + size_t spare; + void *spare2; + size_t data_segment_len; + void *data_segment; +}; + +struct ctl_iscsi_receive_params { + int connection_id; + void *bhs; + size_t spare; + void *spare2; + size_t data_segment_len; + void *data_segment; +}; + +struct ctl_iscsi_close_params { + int connection_id; +}; +#endif /* ICL_KERNEL_PROXY */ + +union ctl_iscsi_data { + struct ctl_iscsi_handoff_params handoff; + struct ctl_iscsi_list_params list; + struct ctl_iscsi_logout_params logout; + struct ctl_iscsi_terminate_params terminate; +#ifdef ICL_KERNEL_PROXY + struct ctl_iscsi_listen_params listen; + struct ctl_iscsi_accept_params accept; + struct ctl_iscsi_send_params send; + struct ctl_iscsi_receive_params receive; + struct ctl_iscsi_close_params close; +#endif +}; + +/* + * iSCSI interface + * + * status: The status of the request. See above for the + * description of the values of this field. + * + * error_str: If the status indicates an error, this string will + * be filled in to describe the error. + */ +struct ctl_iscsi { + ctl_iscsi_type type; /* passed to kernel */ + union ctl_iscsi_data data; /* passed to kernel */ + ctl_iscsi_status status; /* passed to userland */ + char error_str[CTL_ERROR_STR_LEN]; + /* passed to userland */ +}; + #define CTL_IO _IOWR(CTL_MINOR, 0x00, union ctl_io) #define CTL_ENABLE_PORT _IOW(CTL_MINOR, 0x04, struct ctl_port_entry) #define CTL_DISABLE_PORT _IOW(CTL_MINOR, 0x05, struct ctl_port_entry) @@ -612,6 +780,7 @@ struct ctl_lun_list { #define CTL_LUN_LIST _IOWR(CTL_MINOR, 0x22, struct ctl_lun_list) #define CTL_ERROR_INJECT_DELETE _IOW(CTL_MINOR, 0x23, struct ctl_error_desc) #define CTL_SET_PORT_WWNS _IOW(CTL_MINOR, 0x24, struct ctl_port_entry) +#define CTL_ISCSI _IOWR(CTL_MINOR, 0x25, struct ctl_iscsi) #endif /* _CTL_IOCTL_H_ */ -- cgit v1.1