/*- * Copyright (c) 2005-2008 Daniel Braniss * All rights reserved. * * 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: src/sys/dev/iscsi/initiator/iscsi.c,v 1.4 2008/11/25 07:17:11 scottl Exp $ */ /* | iSCSI | $Id: iscsi.c,v 1.35 2007/04/22 08:58:29 danny Exp danny $ */ #include "opt_iscsi_initiator.h" #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 static char *iscsi_driver_version = "2.1.0"; static struct isc_softc isc; MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI driver"); struct objcache_malloc_args iscsi_malloc_args = { sizeof(pduq_t), M_ISCSI }; #ifdef ISCSI_INITIATOR_DEBUG int iscsi_debug = ISCSI_INITIATOR_DEBUG; SYSCTL_INT(_debug, OID_AUTO, iscsi_initiator, CTLFLAG_RW, &iscsi_debug, 0, "iSCSI driver debug flag"); struct lock iscsi_dbg_lock; #endif static char isid[6+1] = { 0x80, 'D', 'I', 'B', '0', '0', 0 }; static int i_create_session(struct cdev *dev, int *ndev); static int i_ping(struct cdev *dev); static int i_send(struct cdev *dev, caddr_t arg, struct thread *td); static int i_recv(struct cdev *dev, caddr_t arg, struct thread *td); static int i_setsoc(isc_session_t *sp, int fd, struct thread *td); static void free_pdus(struct isc_softc *sc); static d_open_t iscsi_open; static d_close_t iscsi_close; static d_ioctl_t iscsi_ioctl; #ifdef ISCSI_INITIATOR_DEBUG static d_read_t iscsi_read; #endif static struct dev_ops iscsi_ops = { .head = { "iscsi", 0, D_DISK}, .d_open = iscsi_open, .d_close = iscsi_close, .d_ioctl = iscsi_ioctl, #ifdef ISCSI_INITIATOR_DEBUG .d_read = iscsi_read, #endif }; static int iscsi_open(struct dev_open_args *ap) { cdev_t dev = ap->a_head.a_dev; debug_called(8); debug(7, "dev=%d", dev->si_uminor); if(minor(dev) > MAX_SESSIONS) { // should not happen return ENODEV; } /* Make sure the device is passed */ if (dev->si_drv1 == NULL) dev->si_drv1 = (struct isc *)isc.dev->si_drv1; if(minor(dev) == MAX_SESSIONS) { #if 1 struct isc_softc *sc = (struct isc_softc *)dev->si_drv1; // this should be in iscsi_start if(sc->cam_sim == NULL) ic_init(sc); #endif } return 0; } static int iscsi_close(struct dev_close_args *ap) { cdev_t dev = ap->a_head.a_dev; int flag = ap->a_fflag; isc_session_t *sp; debug_called(8); debug(3, "flag=%x", flag); if(minor(dev) == MAX_SESSIONS) { return 0; } sp = (isc_session_t *)dev->si_drv2; if(sp != NULL) { sdebug(2, "session=%d flags=%x", minor(dev), sp->flags ); /* | if still in full phase, this probably means | that something went realy bad. | it could be a result from 'shutdown', in which case | we will ignore it (so buffers can be flushed). | the problem is that there is no way of differentiating | between a shutdown procedure and 'iscontrol' dying. */ if(sp->flags & ISC_FFPHASE) // delay in case this is a shutdown. tsleep(sp, 0, "isc-cls", 60*hz); ism_stop(sp); } debug(2, "done"); return 0; } static int iscsi_ioctl(struct dev_ioctl_args *ap) { struct isc *sc; cdev_t dev = ap->a_head.a_dev; caddr_t arg = ap->a_data; isc_session_t *sp; isc_opt_t *opt; int error; sc = (struct isc *)dev->si_drv1; debug_called(8); error = 0; if(minor(dev) == MAX_SESSIONS) { /* | non Session commands */ if(sc == NULL) return ENXIO; switch(ap->a_cmd) { case ISCSISETSES: error = i_create_session(dev, (int *)arg); if(error == 0) break; default: error = ENXIO; // XXX: } return error; } sp = (isc_session_t *)dev->si_drv2; /* | session commands */ if(sp == NULL) return ENXIO; sdebug(6, "dev=%d cmd=%d", minor(dev), (int)(ap->a_cmd & 0xff)); switch(ap->a_cmd) { case ISCSISETSOC: error = i_setsoc(sp, *(u_int *)arg, curthread); break; case ISCSISETOPT: opt = (isc_opt_t *)arg; error = i_setopt(sp, opt); break; case ISCSISEND: error = i_send(dev, arg, curthread); break; case ISCSIRECV: error = i_recv(dev, arg, curthread); break; case ISCSIPING: error = i_ping(dev); break; case ISCSISTART: error = sp->soc == NULL? ENOTCONN: ism_fullfeature(dev, 1); if(error == 0) { sp->proc = curthread->td_proc; SYSCTL_ADD_UINT(&sp->clist, SYSCTL_CHILDREN(sp->oid), OID_AUTO, "pid", CTLFLAG_RD, &sp->proc->p_pid, sizeof(pid_t), "control process id"); } break; case ISCSIRESTART: error = sp->soc == NULL? ENOTCONN: ism_fullfeature(dev, 2); break; case ISCSISTOP: error = ism_fullfeature(dev, 0); break; case ISCSISIGNAL: { int sig = *(int *)arg; if(sig < 0 || sig > _SIG_MAXSIG) error = EINVAL; else sp->signal = sig; break; } case ISCSIGETCAM: { iscsi_cam_t *cp = (iscsi_cam_t *)arg; error = ic_getCamVals(sp, cp); break; } default: error = ENOIOCTL; } return error; } static int iscsi_read(struct dev_read_args *ra) { #ifdef ISCSI_INITIATOR_DEBUG struct isc_softc *sc; cdev_t dev = ra->a_head.a_dev; struct uio *uio = ra->a_uio; isc_session_t *sp; pduq_t *pq; char buf[1024]; sc = (struct isc_softc *)dev->si_drv1; sp = (isc_session_t *)dev->si_drv2; if(minor(dev) == MAX_SESSIONS) { ksprintf(buf, "/----- Session ------/\n"); uiomove(buf, strlen(buf), uio); int i = 0; TAILQ_FOREACH(sp, &sc->isc_sess, sp_link) { if(uio->uio_resid == 0) return 0; ksprintf(buf, "%03d] '%s' '%s'\n", i++, sp->opt.targetAddress, sp->opt.targetName); uiomove(buf, strlen(buf), uio); } ksprintf(buf, "%d/%d /---- free -----/\n", sc->npdu_alloc, sc->npdu_max); i = 0; uiomove(buf, strlen(buf), uio); TAILQ_FOREACH(pq, &sc->freepdu, pq_link) { if(uio->uio_resid == 0) return 0; ksprintf(buf, "%03d] %06x\n", i++, ntohl(pq->pdu.ipdu.bhs.itt)); uiomove(buf, strlen(buf), uio); } } else { int i = 0; struct socket *so = sp->soc; #define pukeit(i, pq) do {\ ksprintf(buf, "%03d] %06x %02x %x %ld\n",\ i, ntohl( pq->pdu.ipdu.bhs.CmdSN), \ pq->pdu.ipdu.bhs.opcode, ntohl(pq->pdu.ipdu.bhs.itt),\ (long)pq->ts.tv_sec);\ } while(0) ksprintf(buf, "%d/%d /---- hld -----/\n", sp->stats.nhld, sp->stats.max_hld); uiomove(buf, strlen(buf), uio); TAILQ_FOREACH(pq, &sp->hld, pq_link) { if(uio->uio_resid == 0) return 0; pukeit(i, pq); i++; uiomove(buf, strlen(buf), uio); } ksprintf(buf, "%d/%d /---- rsp -----/\n", sp->stats.nrsp, sp->stats.max_rsp); uiomove(buf, strlen(buf), uio); i = 0; TAILQ_FOREACH(pq, &sp->rsp, pq_link) { if(uio->uio_resid == 0) return 0; pukeit(i, pq); i++; uiomove(buf, strlen(buf), uio); } ksprintf(buf, "%d/%d /---- csnd -----/\n", sp->stats.ncsnd, sp->stats.max_csnd); i = 0; uiomove(buf, strlen(buf), uio); TAILQ_FOREACH(pq, &sp->csnd, pq_link) { if(uio->uio_resid == 0) return 0; pukeit(i, pq); i++; uiomove(buf, strlen(buf), uio); } ksprintf(buf, "%d/%d /---- wsnd -----/\n", sp->stats.nwsnd, sp->stats.max_wsnd); i = 0; uiomove(buf, strlen(buf), uio); TAILQ_FOREACH(pq, &sp->wsnd, pq_link) { if(uio->uio_resid == 0) return 0; pukeit(i, pq); i++; uiomove(buf, strlen(buf), uio); } ksprintf(buf, "%d/%d /---- isnd -----/\n", sp->stats.nisnd, sp->stats.max_isnd); i = 0; uiomove(buf, strlen(buf), uio); TAILQ_FOREACH(pq, &sp->isnd, pq_link) { if(uio->uio_resid == 0) return 0; pukeit(i, pq); i++; uiomove(buf, strlen(buf), uio); } ksprintf(buf, "/---- Stats ---/\n"); uiomove(buf, strlen(buf), uio); ksprintf(buf, "recv=%d sent=%d\n", sp->stats.nrecv, sp->stats.nsent); uiomove(buf, strlen(buf), uio); ksprintf(buf, "flags=%x pdus: alloc=%d max=%d\n", sp->flags, sc->npdu_alloc, sc->npdu_max); uiomove(buf, strlen(buf), uio); ksprintf(buf, "cws=%d last cmd=%x exp=%x max=%x stat=%x itt=%x\n", sp->cws, sp->sn.cmd, sp->sn.expCmd, sp->sn.maxCmd, sp->sn.stat, sp->sn.itt); uiomove(buf, strlen(buf), uio); if (so) ksprintf(buf, "/---- socket -----/\nso_state=%x\n", so->so_state); uiomove(buf, strlen(buf), uio); } #endif return 0; } static int i_ping(struct cdev *dev) { return 0; } /* | low level I/O */ static int i_setsoc(isc_session_t *sp, int fd, thread_t td) { int error = 0; struct file *fp; if (sp->soc != NULL) isc_stop_receiver(sp); if (sp->fp) { fdrop(sp->fp); sp->fp = NULL; } debug_called(8); if ((error = holdsock(td, fd, &fp)) == 0) { sp->soc = fp->f_data; sp->fp = fp; isc_start_receiver(sp); } return error; } static int i_send(struct cdev *dev, caddr_t arg, struct thread *td) { isc_session_t *sp = (isc_session_t *)dev->si_drv2; struct isc_softc *sc = (struct isc_softc *)dev->si_drv1; caddr_t bp; pduq_t *pq; pdu_t *pp; int n, error; debug_called(8); if(sp->soc == NULL) return ENOTCONN; if((pq = pdu_alloc(sc, M_NOWAIT)) == NULL) return EAGAIN; pp = &pq->pdu; pq->pdu = *(pdu_t *)arg; pq->refcnt = 0; if((error = i_prepPDU(sp, pq)) != 0) goto out; sdebug(3, "len=%d ahs_len=%d ds_len=%d", pq->len, pp->ahs_len, pp->ds_len); pq->buf = bp = kmalloc(pq->len - sizeof(union ipdu_u), M_ISCSI, M_NOWAIT); if(pq->buf == NULL) { error = EAGAIN; goto out; } if(pp->ahs_len) { n = pp->ahs_len; error = copyin(pp->ahs, bp, n); if(error != 0) { sdebug(3, "copyin ahs: error=%d", error); goto out; } pp->ahs = (ahs_t *)bp; bp += n; } if(pp->ds_len) { n = pp->ds_len; error = copyin(pp->ds, bp, n); if(error != 0) { sdebug(3, "copyin ds: error=%d", error); goto out; } pp->ds = bp; bp += n; while(n & 03) { n++; *bp++ = 0; } } error = isc_qout(sp, pq); #if 1 if(error == 0) wakeup(&sp->flags); // XXX: to 'push' proc_out ... #endif out: if(error) pdu_free(sc, pq); return error; } /* | NOTE: must calculate digest if requiered. */ static int i_recv(struct cdev *dev, caddr_t arg, struct thread *td) { isc_session_t *sp = (isc_session_t *)dev->si_drv2; pduq_t *pq; pdu_t *pp, *up; caddr_t bp; int error, mustfree, cnt; size_t need, have, n; debug_called(8); if(sp == NULL) return EIO; if(sp->soc == NULL) return ENOTCONN; sdebug(3, ""); cnt = 6; // XXX: maybe the user can request a time out? iscsi_lock_ex(&sp->rsp_mtx); while((pq = TAILQ_FIRST(&sp->rsp)) == NULL) { issleep(&sp->rsp, &sp->rsp_mtx, 0, "isc_rsp", hz*10); if(cnt-- == 0) break; // XXX: for now, needs work } if(pq != NULL) { sp->stats.nrsp--; TAILQ_REMOVE(&sp->rsp, pq, pq_link); } iscsi_unlock_ex(&sp->rsp_mtx); sdebug(4, "cnt=%d", cnt); if(pq == NULL) { error = ENOTCONN; sdebug(3, "error=%d sp->flags=%x ", error, sp->flags); return error; } up = (pdu_t *)arg; pp = &pq->pdu; up->ipdu = pp->ipdu; n = 0; up->ds_len = 0; up->ahs_len = 0; error = 0; if(pq->mp) { u_int len; // Grr... len = 0; if(pp->ahs_len) { len += pp->ahs_len; if(sp->hdrDigest) len += 4; } if(pp->ds_len) { len += pp->ds_len; if(sp->hdrDigest) len += 4; } mustfree = 0; if(len > pq->mp->m_len) { mustfree++; bp = kmalloc(len, M_ISCSI, M_INTWAIT); sdebug(4, "need mbufcopy: %d", len); i_mbufcopy(pq->mp, bp, len); } else bp = mtod(pq->mp, caddr_t); if(pp->ahs_len) { need = pp->ahs_len; if(sp->hdrDigest) need += 4; n = MIN(up->ahs_size, need); error = copyout(bp, (caddr_t)up->ahs, n); up->ahs_len = n; bp += need; } if(!error && pp->ds_len) { need = pp->ds_len; if(sp->hdrDigest) need += 4; if((have = up->ds_size) == 0) { have = up->ahs_size - n; up->ds = (caddr_t)up->ahs + n; } n = MIN(have, need); error = copyout(bp, (caddr_t)up->ds, n); up->ds_len = n; } if(mustfree) kfree(bp, M_ISCSI); } sdebug(6, "len=%d ahs_len=%d ds_len=%d", pq->len, pp->ahs_len, pp->ds_len); pdu_free(sp->isc, pq); return error; } static int i_create_session(struct cdev *dev, int *ndev) { struct isc_softc *sc = (struct isc_softc *)dev->si_drv1; isc_session_t *sp; int error, n; debug_called(8); sp = (isc_session_t *)kmalloc(sizeof *sp, M_ISCSI, M_WAITOK | M_ZERO); if(sp == NULL) return ENOMEM; lockmgr(&sc->lock, LK_EXCLUSIVE); /* | search for the lowest unused sid */ for(n = 0; n < MAX_SESSIONS; n++) if(sc->sessions[n] == NULL) break; if(n == MAX_SESSIONS) { lockmgr(&sc->lock, LK_RELEASE); kfree(sp, M_ISCSI); return EPERM; } TAILQ_INSERT_TAIL(&sc->isc_sess, sp, sp_link); sc->nsess++; lockmgr(&sc->lock, LK_RELEASE); sc->sessions[n] = sp; debug(8, "n is %d", n); sp->dev = make_dev(&iscsi_ops, n, UID_ROOT, GID_WHEEL, 0600, "iscsi%d", n); devfs_config(); reference_dev(sp->dev); udev_dict_set_cstr(sp->dev, "subsystem", "disk"); udev_dict_set_cstr(sp->dev, "disk-type", "network"); *ndev = sp->sid = n; sp->isc = sc; sp->dev->si_drv1 = sc; sp->dev->si_drv2 = sp; sp->opt.maxRecvDataSegmentLength = 8192; sp->opt.maxXmitDataSegmentLength = 8192; sp->opt.maxBurstLength = 65536; // 64k sdebug(2, "sessionID=%d sp=%p", n, sp); error = ism_start(sp); return error; } #ifdef notused static void iscsi_counters(isc_session_t *sp) { int h, r, s; pduq_t *pq; #define _puke(i, pq) do {\ debug(2, "%03d] %06x %02x %x %ld %jd %x\n",\ i, ntohl( pq->pdu.ipdu.bhs.CmdSN), \ pq->pdu.ipdu.bhs.opcode, ntohl(pq->pdu.ipdu.bhs.itt),\ (long)pq->ts.sec, pq->ts.frac, pq->flags);\ } while(0) h = r = s = 0; TAILQ_FOREACH(pq, &sp->hld, pq_link) { _puke(h, pq); h++; } TAILQ_FOREACH(pq, &sp->rsp, pq_link) r++; TAILQ_FOREACH(pq, &sp->csnd, pq_link) s++; TAILQ_FOREACH(pq, &sp->wsnd, pq_link) s++; TAILQ_FOREACH(pq, &sp->isnd, pq_link) s++; debug(2, "hld=%d rsp=%d snd=%d", h, r, s); } #endif static void iscsi_shutdown(void *v) { struct isc_softc *sc = (struct isc_softc *)v; isc_session_t *sp; int n; debug_called(8); if(sc == NULL) { xdebug("sc is NULL!"); return; } if(sc->eh == NULL) debug(2, "sc->eh is NULL"); else { EVENTHANDLER_DEREGISTER(shutdown_pre_sync, sc->eh); debug(2, "done n=%d", sc->nsess); } n = 0; TAILQ_FOREACH(sp, &sc->isc_sess, sp_link) { debug(2, "%2d] sp->flags=0x%08x", n, sp->flags); n++; } debug(2, "done"); } static int init_pdus(struct isc_softc *sc) { debug_called(8); sc->pdu_zone = objcache_create("pdu", 0, 0, NULL, NULL, NULL, objcache_malloc_alloc, objcache_malloc_free, &iscsi_malloc_args); if(sc->pdu_zone == NULL) { kprintf("iscsi_initiator: objcache_create failed"); return -1; } TAILQ_INIT(&sc->freepdu); return 0; } static void free_pdus(struct isc_softc *sc) { pduq_t *pq; debug_called(8); if(sc->pdu_zone != NULL) { TAILQ_FOREACH(pq, &sc->freepdu, pq_link) { TAILQ_REMOVE(&sc->freepdu, pq, pq_link); objcache_put(sc->pdu_zone, pq); } objcache_destroy(sc->pdu_zone); sc->pdu_zone = NULL; } } static void iscsi_start(void) { struct isc_softc *sc = &isc; debug_called(8); memset(sc, 0, sizeof(struct isc_softc)); sc->dev = make_dev(&iscsi_ops, MAX_SESSIONS, UID_ROOT, GID_WHEEL, 0600, "iscsi"); devfs_config(); sc->dev->si_drv1 = sc; reference_dev(sc->dev); TAILQ_INIT(&sc->isc_sess); if(init_pdus(sc) != 0) xdebug("pdu zone init failed!"); // XXX: should cause terminal failure ... lockinit(&sc->lock, "iscsi", 0, LK_CANRECURSE); lockinit(&sc->pdu_lock, "iscsi pdu pool", 0, LK_CANRECURSE); #if 0 // XXX: this will cause a panic if the // module is loaded too early if(ic_init(sc) != 0) return; #else sc->cam_sim = NULL; #endif #ifdef DO_EVENTHANDLER if((sc->eh = EVENTHANDLER_REGISTER(shutdown_pre_sync, iscsi_shutdown, sc, SHUTDOWN_PRI_DEFAULT-1)) == NULL) xdebug("shutdown event registration failed\n"); #endif /* | sysctl stuff */ sysctl_ctx_init(&sc->clist); sc->oid = SYSCTL_ADD_NODE(&sc->clist, SYSCTL_STATIC_CHILDREN(_net), OID_AUTO, "iscsi", CTLFLAG_RD, 0, "iSCSI Subsystem"); SYSCTL_ADD_STRING(&sc->clist, SYSCTL_CHILDREN(sc->oid), OID_AUTO, "driver_version", CTLFLAG_RD, iscsi_driver_version, 0, "iscsi driver version"); SYSCTL_ADD_STRING(&sc->clist, SYSCTL_CHILDREN(sc->oid), OID_AUTO, "isid", CTLFLAG_RW, isid, 6+1, "initiator part of the Session Identifier"); SYSCTL_ADD_INT(&sc->clist, SYSCTL_CHILDREN(sc->oid), OID_AUTO, "sessions", CTLFLAG_RD, &sc->nsess, sizeof(sc->nsess), "number of active session"); kprintf("iscsi: version %s\n", iscsi_driver_version); } /* | Notes: | unload SHOULD fail if there is activity | activity: there is/are active session/s */ static void iscsi_stop(void) { struct isc_softc *sc = &isc; isc_session_t *sp, *sp_tmp; debug_called(8); /* | go through all the sessions | Note: close should have done this ... */ TAILQ_FOREACH_MUTABLE(sp, &sc->isc_sess, sp_link, sp_tmp) { //XXX: check for activity ... ism_stop(sp); } if(sc->cam_sim != NULL) ic_destroy(sc); lockuninit(&sc->lock); lockuninit(&sc->pdu_lock); free_pdus(sc); if(sc->dev) { release_dev(sc->dev); destroy_dev(sc->dev); //dev_ops_remove(&sc->dev, -1, 0); } if(sysctl_ctx_free(&sc->clist)) xdebug("sysctl_ctx_free failed"); iscsi_shutdown(sc); // XXX: check EVENTHANDLER_ ... } static int iscsi_modevent(module_t mod, int what, void *arg) { debug_called(8); switch(what) { case MOD_LOAD: iscsi_start(); break; case MOD_SHUTDOWN: break; case MOD_UNLOAD: iscsi_stop(); break; default: break; } return 0; } moduledata_t iscsi_mod = { "iscsi_initiator", (modeventhand_t) iscsi_modevent, 0 }; #ifdef ISCSI_ROOT static void iscsi_rootconf(void) { #if 0 nfs_setup_diskless(); if (nfs_diskless_valid) rootdevnames[0] = "nfs:"; #endif kprintf("** iscsi_rootconf **\n"); } SYSINIT(cpu_rootconf1, SI_SUB_ROOT_CONF, SI_ORDER_FIRST, iscsi_rootconf, NULL); #endif DECLARE_MODULE(iscsi_initiator, iscsi_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(iscsi_initiator, cam, 1, 1, 1);