root/usr/src/uts/common/io/timod.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Transport Interface Library cooperating module - issue 2
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/timod.h>
#include <sys/suntpi.h>
#include <sys/debug.h>
#include <sys/strlog.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/sysmacros.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/strsun.h>
#include <c2/audit.h>

/*
 * This is the loadable module wrapper.
 */
#include <sys/conf.h>
#include <sys/modctl.h>

static struct streamtab timinfo;

static struct fmodsw fsw = {
        "timod",
        &timinfo,
        D_MTQPAIR | D_MP,
};

/*
 * Module linkage information for the kernel.
 */

static struct modlstrmod modlstrmod = {
        &mod_strmodops, "transport interface str mod", &fsw
};

static struct modlinkage modlinkage = {
        MODREV_1, &modlstrmod, NULL
};

static krwlock_t        tim_list_rwlock;

/*
 * This module keeps track of capabilities of underlying transport. Information
 * is persistent through module invocations (open/close). Currently it remembers
 * whether underlying transport supports TI_GET{MY,PEER}NAME ioctls and
 * T_CAPABILITY_REQ message. This module either passes ioctl/messages to the
 * transport or emulates it when transport doesn't understand these
 * ioctl/messages.
 *
 * It is assumed that transport supports T_CAPABILITY_REQ when timod receives
 * T_CAPABILITY_ACK from the transport. There is no current standard describing
 * transport behaviour when it receives unknown message type, so following
 * reactions are expected and handled:
 *
 * 1) Transport drops unknown T_CAPABILITY_REQ message type. In this case timod
 *    will wait for tcap_wait time and assume that transport doesn't provide
 *    this message type. T_CAPABILITY_REQ should never travel over the wire, so
 *    timeout value should only take into consideration internal processing time
 *    for the message. From user standpoint it may mean that an application will
 *    hang for TCAP_WAIT time in the kernel the first time this message is used
 *    with some particular transport (e.g. TCP/IP) during system uptime.
 *
 * 2) Transport responds with T_ERROR_ACK specifying T_CAPABILITY_REQ as
 *    original message type. In this case it is assumed that transport doesn't
 *    support it (which may not always be true - some transports return
 *    T_ERROR_ACK in other cases like lack of system memory).
 *
 * 3) Transport responds with M_ERROR, effectively shutting down the
 *    stream. Unfortunately there is no standard way to pass the reason of
 *    M_ERROR message back to the caller, so it is assumed that if M_ERROR was
 *    sent in response to T_CAPABILITY_REQ message, transport doesn't support
 *    it.
 *
 * It is possible under certain circumstances that timod will incorrectly assume
 * that underlying transport doesn't provide T_CAPABILITY_REQ message type. In
 * this "worst-case" scenario timod will emulate its functionality by itself and
 * will provide only TC1_INFO capability. All other bits in CAP_bits1 field are
 * cleaned. TC1_INFO is emulated by sending T_INFO_REQ down to transport
 * provider.
 */

/*
 * Notes about locking:
 *
 * tim_list_rwlock protects the list of tim_tim structures itself.  When this
 * lock is held, the list itself is stable, but the contents of the entries
 * themselves might not be.
 *
 * The rest of the members are generally protected by D_MTQPAIR, which
 * specifies a default exclusive inner perimeter.  If you're looking at
 * q->q_ptr, then it's stable.
 *
 * There's one exception to this rule: tim_peer{maxlen,len,name}.  These members
 * are touched without entering the associated STREAMS perimeter because we
 * get the pointer via tim_findlink() rather than q_ptr.  These are protected
 * by tim_mutex instead.  If you don't hold that lock, don't look at them.
 *
 * (It would be possible to separate out the 'set by T_CONN_RES' cases from the
 * others, but there appears to be no reason to do so.)
 */
struct tim_tim {
        uint32_t        tim_flags;
        t_uscalar_t     tim_backlog;
        mblk_t          *tim_iocsave;
        t_scalar_t      tim_mymaxlen;
        t_scalar_t      tim_mylen;
        caddr_t         tim_myname;
        t_scalar_t      tim_peermaxlen;
        t_scalar_t      tim_peerlen;
        caddr_t         tim_peername;
        cred_t          *tim_peercred;
        mblk_t          *tim_consave;
        bufcall_id_t    tim_wbufcid;
        bufcall_id_t    tim_rbufcid;
        timeout_id_t    tim_wtimoutid;
        timeout_id_t    tim_rtimoutid;
        /* Protected by the global tim_list_rwlock for all instances */
        struct tim_tim  *tim_next;
        struct tim_tim  **tim_ptpn;
        t_uscalar_t     tim_acceptor;
        t_scalar_t      tim_saved_prim;         /* Primitive from message */
                                                /*  part of ioctl. */
        timeout_id_t    tim_tcap_timoutid;      /* For T_CAP_REQ timeout */
        tpi_provinfo_t  *tim_provinfo;          /* Transport description */
        kmutex_t        tim_mutex;              /* protect tim_peer* */
        pid_t           tim_cpid;
};


/*
 * Local flags used with tim_flags field in instance structure of
 * type 'struct _ti_user' declared above.
 * Historical note:
 * This namespace constants were previously declared in a
 * a very messed up namespace in timod.h
 *
 * There may be 3 states for transport:
 *
 * 1) It provides T_CAPABILITY_REQ
 * 2) It does not provide T_CAPABILITY_REQ
 * 3) It is not known yet whether transport provides T_CAPABILITY_REQ or not.
 *
 * It is assumed that the underlying transport either provides
 * T_CAPABILITY_REQ or not and this does not changes during the
 * system lifetime.
 *
 */
#define PEEK_RDQ_EXPIND 0x0001  /* look for expinds on stream rd queues */
#define WAITIOCACK      0x0002  /* waiting for info for ioctl act       */
#define CLTS            0x0004  /* connectionless transport             */
#define COTS            0x0008  /* connection-oriented transport        */
#define CONNWAIT        0x0010  /* waiting for connect confirmation     */
#define LOCORDREL       0x0020  /* local end has orderly released       */
#define REMORDREL       0x0040  /* remote end had orderly released      */
#define NAMEPROC        0x0080  /* processing a NAME ioctl              */
#define DO_MYNAME       0x0100  /* timod handles TI_GETMYNAME           */
#define DO_PEERNAME     0x0200  /* timod handles TI_GETPEERNAME         */
#define TI_CAP_RECVD    0x0400  /* TI_CAPABILITY received               */
#define CAP_WANTS_INFO  0x0800  /* TI_CAPABILITY has TC1_INFO set       */
#define WAIT_IOCINFOACK 0x1000  /* T_INFO_REQ generated from ioctl      */
#define WAIT_CONNRESACK 0x2000  /* waiting for T_OK_ACK to T_CONN_RES   */


/* Debugging facilities */
/*
 * Logging needed for debugging timod should only appear in DEBUG kernel.
 */
#ifdef DEBUG
#define TILOG(msg, arg)         tilog((msg), (arg))
#define TILOGP(msg, arg)        tilogp((msg), (arg))
#else
#define TILOG(msg, arg)
#define TILOGP(msg, arg)
#endif


/*
 * Sleep timeout for T_CAPABILITY_REQ. This message never travels across
 * network, so timeout value should be enough to cover all internal processing
 * time.
 */
clock_t tim_tcap_wait = 2;

/* Sleep timeout in tim_recover() */
#define TIMWAIT (1*hz)
/* Sleep timeout in tim_ioctl_retry() 0.2 seconds */
#define TIMIOCWAIT      (200*hz/1000)

/*
 * Return values for ti_doname().
 */
#define DONAME_FAIL     0       /* failing ioctl (done) */
#define DONAME_DONE     1       /* done processing */
#define DONAME_CONT     2       /* continue proceesing (not done yet) */

/*
 * Function prototypes
 */
static int ti_doname(queue_t *, mblk_t *);
static int ti_expind_on_rdqueues(queue_t *);
static void tim_ioctl_send_reply(queue_t *, mblk_t *, mblk_t *);
static void tim_send_ioc_error_ack(queue_t *, struct tim_tim *, mblk_t *);
static void tim_tcap_timer(void *);
static void tim_tcap_genreply(queue_t *, struct tim_tim *);
static void tim_send_reply(queue_t *, mblk_t *, struct tim_tim *, t_scalar_t);
static void tim_answer_ti_sync(queue_t *, mblk_t *, struct tim_tim *,
    mblk_t *, uint32_t);
static void tim_send_ioctl_tpi_msg(queue_t *, mblk_t *, struct tim_tim *,
        struct iocblk *);
static void tim_clear_peer(struct tim_tim *);

int
_init(void)
{
        int     error;

        rw_init(&tim_list_rwlock, NULL, RW_DRIVER, NULL);
        error = mod_install(&modlinkage);
        if (error != 0) {
                rw_destroy(&tim_list_rwlock);
                return (error);
        }

        return (0);
}

int
_fini(void)
{
        int     error;

        error = mod_remove(&modlinkage);
        if (error != 0)
                return (error);
        rw_destroy(&tim_list_rwlock);
        return (0);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}


/*
 * Hash list for all instances. Used to find tim_tim structure based on
 * ACCEPTOR_id in T_CONN_RES. Protected by tim_list_rwlock.
 */
#define TIM_HASH_SIZE   256
#ifdef  _ILP32
#define TIM_HASH(id) (((uintptr_t)(id) >> 8) % TIM_HASH_SIZE)
#else
#define TIM_HASH(id) ((uintptr_t)(id) % TIM_HASH_SIZE)
#endif  /* _ILP32 */
static struct tim_tim   *tim_hash[TIM_HASH_SIZE];
int             tim_cnt = 0;

static void tilog(char *, t_scalar_t);
static void tilogp(char *, uintptr_t);
static mblk_t *tim_filladdr(queue_t *, mblk_t *, boolean_t);
static void tim_addlink(struct tim_tim  *);
static void tim_dellink(struct tim_tim  *);
static struct tim_tim *tim_findlink(t_uscalar_t);
static void tim_recover(queue_t *, mblk_t *, t_scalar_t);
static void tim_ioctl_retry(queue_t *);

int dotilog = 0;

#define TIMOD_ID        3

static int timodopen(queue_t *, dev_t *, int, int, cred_t *);
static int timodclose(queue_t *, int, cred_t *);
static int timodwput(queue_t *, mblk_t *);
static int timodrput(queue_t *, mblk_t *);
static int timodrsrv(queue_t *);
static int timodwsrv(queue_t *);
static int timodrproc(queue_t *, mblk_t *);
static int timodwproc(queue_t *, mblk_t *);

/* stream data structure definitions */

static struct module_info timod_info =
        {TIMOD_ID, "timod", 0, INFPSZ, 512, 128};
static struct qinit timodrinit = {
        timodrput,
        timodrsrv,
        timodopen,
        timodclose,
        nulldev,
        &timod_info,
        NULL
};
static struct qinit timodwinit = {
        timodwput,
        timodwsrv,
        timodopen,
        timodclose,
        nulldev,
        &timod_info,
        NULL
};
static struct streamtab timinfo = { &timodrinit, &timodwinit, NULL, NULL };

/*
 * timodopen -  open routine gets called when the module gets pushed
 *              onto the stream.
 */
/*ARGSUSED*/
static int
timodopen(
        queue_t *q,
        dev_t *devp,
        int flag,
        int sflag,
        cred_t *crp)
{
        struct tim_tim *tp;
        struct stroptions *sop;
        mblk_t *bp;

        ASSERT(q != NULL);

        if (q->q_ptr) {
                return (0);
        }

        if ((bp = allocb(sizeof (struct stroptions), BPRI_MED)) == 0)
                return (ENOMEM);

        tp = kmem_zalloc(sizeof (struct tim_tim), KM_SLEEP);

        tp->tim_cpid = -1;
        tp->tim_saved_prim = -1;

        mutex_init(&tp->tim_mutex, NULL, MUTEX_DEFAULT, NULL);

        q->q_ptr = (caddr_t)tp;
        WR(q)->q_ptr = (caddr_t)tp;

        tilogp("timodopen: Allocated for tp %lx\n", (uintptr_t)tp);
        tilogp("timodopen: Allocated for q %lx\n", (uintptr_t)q);

        /* Must be done before tpi_findprov and _ILP32 q_next walk below */
        qprocson(q);

        tp->tim_provinfo = tpi_findprov(q);

        /*
         * Defer allocation of the buffers for the local address and
         * the peer's address until we need them.
         * Assume that timod has to handle getname until we here
         * an iocack from the transport provider or we know that
         * transport provider doesn't understand it.
         */
        if (tp->tim_provinfo->tpi_myname != PI_YES) {
                TILOG("timodopen: setting DO_MYNAME\n", 0);
                tp->tim_flags |= DO_MYNAME;
        }

        if (tp->tim_provinfo->tpi_peername != PI_YES) {
                TILOG("timodopen: setting DO_PEERNAME\n", 0);
                tp->tim_flags |= DO_PEERNAME;
        }

#ifdef  _ILP32
        {
                queue_t *driverq;

                /*
                 * Find my driver's read queue (for T_CONN_RES handling)
                 */
                driverq = WR(q);
                while (SAMESTR(driverq))
                        driverq = driverq->q_next;

                tp->tim_acceptor = (t_uscalar_t)RD(driverq);
        }
#else
        tp->tim_acceptor = (t_uscalar_t)getminor(*devp);
#endif  /* _ILP32 */

        /*
         * Add this one to the list.
         */
        tim_addlink(tp);

        /*
         * Send M_SETOPTS to stream head to make sure M_PCPROTO messages
         * are not flushed. This prevents application deadlocks.
         */
        bp->b_datap->db_type = M_SETOPTS;
        bp->b_wptr += sizeof (struct stroptions);
        sop = (struct stroptions *)bp->b_rptr;
        sop->so_flags = SO_READOPT;
        sop->so_readopt = RFLUSHPCPROT;

        putnext(q, bp);

        return (0);
}

static void
tim_timer(void *arg)
{
        queue_t *q = arg;
        struct tim_tim *tp = (struct tim_tim *)q->q_ptr;

        ASSERT(tp);

        if (q->q_flag & QREADR) {
                ASSERT(tp->tim_rtimoutid);
                tp->tim_rtimoutid = 0;
        } else {
                ASSERT(tp->tim_wtimoutid);
                tp->tim_wtimoutid = 0;
        }
        enableok(q);
        qenable(q);
}

static void
tim_buffer(void *arg)
{
        queue_t *q = arg;
        struct tim_tim *tp = (struct tim_tim *)q->q_ptr;

        ASSERT(tp);

        if (q->q_flag & QREADR) {
                ASSERT(tp->tim_rbufcid);
                tp->tim_rbufcid = 0;
        } else {
                ASSERT(tp->tim_wbufcid);
                tp->tim_wbufcid = 0;
        }
        enableok(q);
        qenable(q);
}

/*
 * timodclose - This routine gets called when the module gets popped
 * off of the stream.
 */
/*ARGSUSED*/
static int
timodclose(
        queue_t *q,
        int flag,
        cred_t *crp)
{
        struct tim_tim *tp;
        mblk_t *mp;
        mblk_t *nmp;

        ASSERT(q != NULL);

        tp = (struct tim_tim *)q->q_ptr;
        q->q_ptr = NULL;

        ASSERT(tp != NULL);

        tilogp("timodclose: Entered for tp %lx\n", (uintptr_t)tp);
        tilogp("timodclose: Entered for q %lx\n", (uintptr_t)q);

        qprocsoff(q);
        tim_dellink(tp);

        /*
         * Cancel any outstanding bufcall
         * or timeout requests.
         */
        if (tp->tim_wbufcid) {
                qunbufcall(q, tp->tim_wbufcid);
                tp->tim_wbufcid = 0;
        }
        if (tp->tim_rbufcid) {
                qunbufcall(q, tp->tim_rbufcid);
                tp->tim_rbufcid = 0;
        }
        if (tp->tim_wtimoutid) {
                (void) quntimeout(q, tp->tim_wtimoutid);
                tp->tim_wtimoutid = 0;
        }
        if (tp->tim_rtimoutid) {
                (void) quntimeout(q, tp->tim_rtimoutid);
                tp->tim_rtimoutid = 0;
        }

        if (tp->tim_tcap_timoutid != 0) {
                (void) quntimeout(q, tp->tim_tcap_timoutid);
                tp->tim_tcap_timoutid = 0;
        }

        if (tp->tim_iocsave != NULL)
                freemsg(tp->tim_iocsave);
        mp = tp->tim_consave;
        while (mp) {
                nmp = mp->b_next;
                mp->b_next = NULL;
                freemsg(mp);
                mp = nmp;
        }
        ASSERT(tp->tim_mymaxlen >= 0);
        if (tp->tim_mymaxlen != 0)
                kmem_free(tp->tim_myname, (size_t)tp->tim_mymaxlen);
        ASSERT(tp->tim_peermaxlen >= 0);
        if (tp->tim_peermaxlen != 0)
                kmem_free(tp->tim_peername, (size_t)tp->tim_peermaxlen);

        q->q_ptr = WR(q)->q_ptr = NULL;

        mutex_destroy(&tp->tim_mutex);

        if (tp->tim_peercred != NULL)
                crfree(tp->tim_peercred);

        kmem_free(tp, sizeof (struct tim_tim));

        return (0);
}

/*
 * timodrput -  Module read put procedure.  This is called from
 *              the module, driver, or stream head upstream/downstream.
 *              Handles M_FLUSH, M_DATA and some M_PROTO (T_DATA_IND,
 *              and T_UNITDATA_IND) messages. All others are queued to
 *              be handled by the service procedures.
 */
static int
timodrput(queue_t *q, mblk_t *mp)
{
        union T_primitives *pptr;

        /*
         * During flow control and other instances when messages
         * are on queue, queue up a non high priority message
         */
        if (q->q_first != 0 && mp->b_datap->db_type < QPCTL) {
                (void) putq(q, mp);
                return (0);
        }

        /*
         * Inline processing of data (to avoid additional procedure call).
         * Rest is handled in timodrproc.
         */

        switch (mp->b_datap->db_type) {
        case M_DATA:
                if (bcanputnext(q, mp->b_band))
                        putnext(q, mp);
                else
                        (void) putq(q, mp);
                break;
        case M_PROTO:
        case M_PCPROTO:
                if (MBLKL(mp) < sizeof (t_scalar_t)) {
                        if (mp->b_datap->db_type == M_PCPROTO ||
                            bcanputnext(q, mp->b_band)) {
                                putnext(q, mp);
                        } else {
                                (void) putq(q, mp);
                        }
                        break;
                }
                pptr = (union T_primitives *)mp->b_rptr;
                switch (pptr->type) {
                case T_EXDATA_IND:
                case T_DATA_IND:
                case T_UNITDATA_IND:
                        if (bcanputnext(q, mp->b_band))
                                putnext(q, mp);
                        else
                                (void) putq(q, mp);
                        break;
                default:
                        (void) timodrproc(q, mp);
                        break;
                }
                break;
        default:
                (void) timodrproc(q, mp);
                break;
        }
        return (0);
}

/*
 * timodrsrv -  Module read queue service procedure.  This is called when
 *              messages are placed on an empty queue, when high priority
 *              messages are placed on the queue, and when flow control
 *              restrictions subside.  This code used to be included in a
 *              put procedure, but it was moved to a service procedure
 *              because several points were added where memory allocation
 *              could fail, and there is no reasonable recovery mechanism
 *              from the put procedure.
 */
/*ARGSUSED*/
static int
timodrsrv(queue_t *q)
{
        mblk_t *mp;
        struct tim_tim *tp;

        ASSERT(q != NULL);

        tp = (struct tim_tim *)q->q_ptr;
        if (!tp)
                return (0);

        while ((mp = getq(q)) != NULL) {
                if (timodrproc(q, mp)) {
                        /*
                         * timodrproc did a putbq - stop processing
                         * messages.
                         */
                        return (0);
                }
        }
        return (0);
}

/*
 * Perform common processing when a T_CAPABILITY_ACK or T_INFO_ACK
 * arrive.  Set the queue properties and adjust the tim_flags according
 * to the service type.
 */
static void
timodprocessinfo(queue_t *q, struct tim_tim *tp, struct T_info_ack *tia)
{
        TILOG("timodprocessinfo: strqset(%d)\n", tia->TIDU_size);
        (void) strqset(q, QMAXPSZ, 0, tia->TIDU_size);
        (void) strqset(OTHERQ(q), QMAXPSZ, 0, tia->TIDU_size);

        if ((tia->SERV_type == T_COTS) || (tia->SERV_type == T_COTS_ORD))
                tp->tim_flags = (tp->tim_flags & ~CLTS) | COTS;
        else if (tia->SERV_type == T_CLTS)
                tp->tim_flags = (tp->tim_flags & ~COTS) | CLTS;
}

static int
timodrproc(queue_t *q, mblk_t *mp)
{
        uint32_t auditing = AU_AUDITING();
        union T_primitives *pptr;
        struct tim_tim *tp;
        struct iocblk *iocbp;
        mblk_t *nbp;
        size_t blen;

        tp = (struct tim_tim *)q->q_ptr;

        switch (mp->b_datap->db_type) {
        default:
                putnext(q, mp);
                break;

        case M_ERROR:
                TILOG("timodrproc: Got M_ERROR, flags = %x\n", tp->tim_flags);
                /*
                 * There is no specified standard response for driver when it
                 * receives unknown message type and M_ERROR is one
                 * possibility. If we send T_CAPABILITY_REQ down and transport
                 * provider responds with M_ERROR we assume that it doesn't
                 * understand this message type. This assumption may be
                 * sometimes incorrect (transport may reply with M_ERROR for
                 * some other reason) but there is no way for us to distinguish
                 * between different cases. In the worst case timod and everyone
                 * else sharing global transport description with it may end up
                 * emulating T_CAPABILITY_REQ.
                 */

                /*
                 * Check that we are waiting for T_CAPABILITY_ACK and
                 * T_CAPABILITY_REQ is not implemented by transport or emulated
                 * by timod.
                 */
                if ((tp->tim_provinfo->tpi_capability == PI_DONTKNOW) &&
                    ((tp->tim_flags & TI_CAP_RECVD) != 0)) {
                        /*
                         * Good chances that this transport doesn't provide
                         * T_CAPABILITY_REQ. Mark this information  permanently
                         * for the module + transport combination.
                         */
                        PI_PROVLOCK(tp->tim_provinfo);
                        if (tp->tim_provinfo->tpi_capability == PI_DONTKNOW)
                                tp->tim_provinfo->tpi_capability = PI_NO;
                        PI_PROVUNLOCK(tp->tim_provinfo);
                        if (tp->tim_tcap_timoutid != 0) {
                                (void) quntimeout(q, tp->tim_tcap_timoutid);
                                tp->tim_tcap_timoutid = 0;
                        }
                }
                putnext(q, mp);
                break;
        case M_DATA:
                if (!bcanputnext(q, mp->b_band)) {
                        (void) putbq(q, mp);
                        return (1);
                }
                putnext(q, mp);
                break;

        case M_PROTO:
        case M_PCPROTO:
                blen = MBLKL(mp);
                if (blen < sizeof (t_scalar_t)) {
                        /*
                         * Note: it's not actually possible to get
                         * here with db_type M_PCPROTO, because
                         * timodrput has already checked MBLKL, and
                         * thus the assertion below.  If the length
                         * was too short, then the message would have
                         * already been putnext'd, and would thus
                         * never appear here.  Just the same, the code
                         * below handles the impossible case since
                         * it's easy to do and saves future
                         * maintainers from unfortunate accidents.
                         */
                        ASSERT(mp->b_datap->db_type == M_PROTO);
                        if (mp->b_datap->db_type == M_PROTO &&
                            !bcanputnext(q, mp->b_band)) {
                                (void) putbq(q, mp);
                                return (1);
                        }
                        putnext(q, mp);
                        break;
                }

                pptr = (union T_primitives *)mp->b_rptr;
                switch (pptr->type) {
                default:

                        if (auditing)
                                audit_sock(T_UNITDATA_IND, q, mp, TIMOD_ID);
                        putnext(q, mp);
                        break;

                case T_ERROR_ACK:
                        /* Restore db_type - recover() might have changed it */
                        mp->b_datap->db_type = M_PCPROTO;
                        if (blen < sizeof (struct T_error_ack)) {
                                putnext(q, mp);
                                break;
                        }

                        tilog("timodrproc: Got T_ERROR_ACK, flags = %x\n",
                            tp->tim_flags);

                        if ((tp->tim_flags & WAIT_CONNRESACK) &&
                            tp->tim_saved_prim == pptr->error_ack.ERROR_prim) {
                                tp->tim_flags &=
                                    ~(WAIT_CONNRESACK | WAITIOCACK);
                                freemsg(tp->tim_iocsave);
                                tp->tim_iocsave = NULL;
                                tp->tim_saved_prim = -1;
                                putnext(q, mp);
                        } else if (tp->tim_flags & WAITIOCACK) {
                                tim_send_ioc_error_ack(q, tp, mp);
                        } else {
                                putnext(q, mp);
                        }
                        break;

                case T_OK_ACK:
                        if (blen < sizeof (pptr->ok_ack)) {
                                mp->b_datap->db_type = M_PCPROTO;
                                putnext(q, mp);
                                break;
                        }

                        tilog("timodrproc: Got T_OK_ACK\n", 0);

                        if (pptr->ok_ack.CORRECT_prim == T_UNBIND_REQ)
                                tp->tim_mylen = 0;

                        if ((tp->tim_flags & WAIT_CONNRESACK) &&
                            tp->tim_saved_prim == pptr->ok_ack.CORRECT_prim) {
                                struct T_conn_res *resp;
                                struct T_conn_ind *indp;
                                struct tim_tim *ntp;
                                caddr_t ptr;

                                rw_enter(&tim_list_rwlock, RW_READER);
                                resp = (struct T_conn_res *)
                                    tp->tim_iocsave->b_rptr;
                                ntp = tim_findlink(resp->ACCEPTOR_id);
                                if (ntp == NULL)
                                        goto cresackout;

                                mutex_enter(&ntp->tim_mutex);
                                if (ntp->tim_peercred != NULL)
                                        crfree(ntp->tim_peercred);
                                ntp->tim_peercred =
                                    msg_getcred(tp->tim_iocsave->b_cont,
                                    &ntp->tim_cpid);
                                if (ntp->tim_peercred != NULL)
                                        crhold(ntp->tim_peercred);

                                if (!(ntp->tim_flags & DO_PEERNAME)) {
                                        mutex_exit(&ntp->tim_mutex);
                                        goto cresackout;
                                }

                                indp = (struct T_conn_ind *)
                                    tp->tim_iocsave->b_cont->b_rptr;
                                /* true as message is put on list */
                                ASSERT(indp->SRC_length >= 0);

                                if (indp->SRC_length > ntp->tim_peermaxlen) {
                                        ptr = kmem_alloc(indp->SRC_length,
                                            KM_NOSLEEP);
                                        if (ptr == NULL) {
                                                mutex_exit(&ntp->tim_mutex);
                                                rw_exit(&tim_list_rwlock);
                                                tilog("timodwproc: kmem_alloc "
                                                    "failed, attempting "
                                                    "recovery\n", 0);
                                                tim_recover(q, mp,
                                                    indp->SRC_length);
                                                return (1);
                                        }
                                        if (ntp->tim_peermaxlen > 0)
                                                kmem_free(ntp->tim_peername,
                                                    ntp->tim_peermaxlen);
                                        ntp->tim_peername = ptr;
                                        ntp->tim_peermaxlen = indp->SRC_length;
                                }
                                ntp->tim_peerlen = indp->SRC_length;
                                ptr = (caddr_t)indp + indp->SRC_offset;
                                bcopy(ptr, ntp->tim_peername, ntp->tim_peerlen);

                                mutex_exit(&ntp->tim_mutex);

                        cresackout:
                                rw_exit(&tim_list_rwlock);
                                tp->tim_flags &=
                                    ~(WAIT_CONNRESACK | WAITIOCACK);
                                freemsg(tp->tim_iocsave);
                                tp->tim_iocsave = NULL;
                                tp->tim_saved_prim = -1;
                        }

                        tim_send_reply(q, mp, tp, pptr->ok_ack.CORRECT_prim);
                        break;

                case T_BIND_ACK: {
                        struct T_bind_ack *ackp =
                            (struct T_bind_ack *)mp->b_rptr;

                        /* Restore db_type - recover() might have changed it */
                        mp->b_datap->db_type = M_PCPROTO;
                        if (blen < sizeof (*ackp)) {
                                putnext(q, mp);
                                break;
                        }

                        /* save negotiated backlog */
                        tp->tim_backlog = ackp->CONIND_number;

                        if (((tp->tim_flags & WAITIOCACK) == 0) ||
                            ((tp->tim_saved_prim != O_T_BIND_REQ) &&
                            (tp->tim_saved_prim != T_BIND_REQ))) {
                                putnext(q, mp);
                                break;
                        }
                        ASSERT(tp->tim_iocsave != NULL);

                        if (tp->tim_flags & DO_MYNAME) {
                                caddr_t p;

                                if (ackp->ADDR_length < 0 ||
                                    mp->b_rptr + ackp->ADDR_offset +
                                    ackp->ADDR_length > mp->b_wptr) {
                                        putnext(q, mp);
                                        break;
                                }
                                if (ackp->ADDR_length > tp->tim_mymaxlen) {
                                        p = kmem_alloc(ackp->ADDR_length,
                                            KM_NOSLEEP);
                                        if (p == NULL) {
                                                tilog("timodrproc: kmem_alloc "
                                                    "failed attempt recovery",
                                                    0);

                                                tim_recover(q, mp,
                                                    ackp->ADDR_length);
                                                return (1);
                                        }
                                        ASSERT(tp->tim_mymaxlen >= 0);
                                        if (tp->tim_mymaxlen != 0) {
                                                kmem_free(tp->tim_myname,
                                                    tp->tim_mymaxlen);
                                        }
                                        tp->tim_myname = p;
                                        tp->tim_mymaxlen = ackp->ADDR_length;
                                }
                                tp->tim_mylen = ackp->ADDR_length;
                                bcopy(mp->b_rptr + ackp->ADDR_offset,
                                    tp->tim_myname, tp->tim_mylen);
                        }
                        tim_ioctl_send_reply(q, tp->tim_iocsave, mp);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(WAITIOCACK | WAIT_IOCINFOACK |
                            TI_CAP_RECVD | CAP_WANTS_INFO);
                        break;
                }

                case T_OPTMGMT_ACK:

                        tilog("timodrproc: Got T_OPTMGMT_ACK\n", 0);

                        /* Restore db_type - recover() might have change it */
                        mp->b_datap->db_type = M_PCPROTO;

                        if (((tp->tim_flags & WAITIOCACK) == 0) ||
                            ((tp->tim_saved_prim != T_SVR4_OPTMGMT_REQ) &&
                            (tp->tim_saved_prim != T_OPTMGMT_REQ))) {
                                putnext(q, mp);
                        } else {
                                ASSERT(tp->tim_iocsave != NULL);
                                tim_ioctl_send_reply(q, tp->tim_iocsave, mp);
                                tp->tim_iocsave = NULL;
                                tp->tim_saved_prim = -1;
                                tp->tim_flags &= ~(WAITIOCACK |
                                    WAIT_IOCINFOACK | TI_CAP_RECVD |
                                    CAP_WANTS_INFO);
                        }
                break;

                case T_INFO_ACK: {
                struct T_info_ack *tia = (struct T_info_ack *)pptr;

                /* Restore db_type - recover() might have changed it */
                mp->b_datap->db_type = M_PCPROTO;

                if (blen < sizeof (*tia)) {
                        putnext(q, mp);
                        break;
                }

                tilog("timodrproc: Got T_INFO_ACK, flags = %x\n",
                    tp->tim_flags);

                timodprocessinfo(q, tp, tia);

                TILOG("timodrproc: flags = %x\n", tp->tim_flags);
                if ((tp->tim_flags & WAITIOCACK) != 0) {
                        size_t  expected_ack_size;
                        ssize_t deficit;
                        int     ioc_cmd;
                        struct T_capability_ack *tcap;

                        /*
                         * The only case when T_INFO_ACK may be received back
                         * when we are waiting for ioctl to complete is when
                         * this ioctl sent T_INFO_REQ down.
                         */
                        if (!(tp->tim_flags & WAIT_IOCINFOACK)) {
                                putnext(q, mp);
                                break;
                        }
                        ASSERT(tp->tim_iocsave != NULL);

                        iocbp = (struct iocblk *)tp->tim_iocsave->b_rptr;
                        ioc_cmd = iocbp->ioc_cmd;

                        /*
                         * Was it sent from TI_CAPABILITY emulation?
                         */
                        if (ioc_cmd == TI_CAPABILITY) {
                                struct T_info_ack       saved_info;

                                /*
                                 * Perform sanity checks. The only case when we
                                 * send T_INFO_REQ from TI_CAPABILITY is when
                                 * timod emulates T_CAPABILITY_REQ and CAP_bits1
                                 * has TC1_INFO set.
                                 */
                                if ((tp->tim_flags &
                                    (TI_CAP_RECVD | CAP_WANTS_INFO)) !=
                                    (TI_CAP_RECVD | CAP_WANTS_INFO)) {
                                        putnext(q, mp);
                                        break;
                                }

                                TILOG("timodrproc: emulating TI_CAPABILITY/"
                                    "info\n", 0);

                                /* Save info & reuse mp for T_CAPABILITY_ACK */
                                saved_info = *tia;

                                mp = tpi_ack_alloc(mp,
                                    sizeof (struct T_capability_ack),
                                    M_PCPROTO, T_CAPABILITY_ACK);

                                if (mp == NULL) {
                                        tilog("timodrproc: realloc failed, "
                                            "no recovery attempted\n", 0);
                                        return (1);
                                }

                                /*
                                 * Copy T_INFO information into T_CAPABILITY_ACK
                                 */
                                tcap = (struct T_capability_ack *)mp->b_rptr;
                                tcap->CAP_bits1 = TC1_INFO;
                                tcap->INFO_ack = saved_info;
                                tp->tim_flags &= ~(WAITIOCACK |
                                    WAIT_IOCINFOACK | TI_CAP_RECVD |
                                    CAP_WANTS_INFO);
                                tim_ioctl_send_reply(q, tp->tim_iocsave, mp);
                                tp->tim_iocsave = NULL;
                                tp->tim_saved_prim = -1;
                                break;
                        }

                        /*
                         * The code for TI_SYNC/TI_GETINFO is left here only for
                         * backward compatibility with staticaly linked old
                         * applications. New TLI/XTI code should use
                         * TI_CAPABILITY for getting transport info and should
                         * not use TI_GETINFO/TI_SYNC for this purpose.
                         */

                        /*
                         * make sure the message sent back is the size of
                         * the "expected ack"
                         * For TI_GETINFO, expected ack size is
                         *      sizeof (T_info_ack)
                         * For TI_SYNC, expected ack size is
                         *      sizeof (struct ti_sync_ack);
                         */
                        if (ioc_cmd != TI_GETINFO && ioc_cmd != TI_SYNC) {
                                putnext(q, mp);
                                break;
                        }

                        expected_ack_size =
                            sizeof (struct T_info_ack); /* TI_GETINFO */
                        if (iocbp->ioc_cmd == TI_SYNC) {
                                expected_ack_size = 2 * sizeof (uint32_t) +
                                    sizeof (struct ti_sync_ack);
                        }
                        deficit = expected_ack_size - blen;

                        if (deficit != 0) {
                                if (mp->b_datap->db_lim - mp->b_wptr <
                                    deficit) {
                                        mblk_t *tmp = allocb(expected_ack_size,
                                            BPRI_HI);
                                        if (tmp == NULL) {
                                                ASSERT(MBLKSIZE(mp) >=
                                                    sizeof (struct T_error_ack));

                                                tilog("timodrproc: allocb failed no "
                                                    "recovery attempt\n", 0);

                                                mp->b_rptr = mp->b_datap->db_base;
                                                pptr = (union T_primitives *)
                                                    mp->b_rptr;
                                                pptr->error_ack.ERROR_prim = T_INFO_REQ;
                                                pptr->error_ack.TLI_error = TSYSERR;
                                                pptr->error_ack.UNIX_error = EAGAIN;
                                                pptr->error_ack.PRIM_type = T_ERROR_ACK;
                                                mp->b_datap->db_type = M_PCPROTO;
                                                tim_send_ioc_error_ack(q, tp, mp);
                                                break;
                                        } else {
                                                bcopy(mp->b_rptr, tmp->b_rptr, blen);
                                                tmp->b_wptr += blen;
                                                pptr = (union T_primitives *)
                                                    tmp->b_rptr;
                                                freemsg(mp);
                                                mp = tmp;
                                        }
                                }
                        }
                        /*
                         * We now have "mp" which has enough space for an
                         * appropriate ack and contains struct T_info_ack
                         * that the transport provider returned. We now
                         * stuff it with more stuff to fullfill
                         * TI_SYNC ioctl needs, as necessary
                         */
                        if (iocbp->ioc_cmd == TI_SYNC) {
                                /*
                                 * Assumes struct T_info_ack is first embedded
                                 * type in struct ti_sync_ack so it is
                                 * automatically there.
                                 */
                                struct ti_sync_ack *tsap =
                                    (struct ti_sync_ack *)mp->b_rptr;

                                /*
                                 * tsap->tsa_qlen needs to be set only if
                                 * TSRF_QLEN_REQ flag is set, but for
                                 * compatibility with statically linked
                                 * applications it is set here regardless of the
                                 * flag since old XTI library expected it to be
                                 * set.
                                 */
                                tsap->tsa_qlen = tp->tim_backlog;
                                tsap->tsa_flags = 0x0; /* intialize clear */
                                if (tp->tim_flags & PEEK_RDQ_EXPIND) {
                                        /*
                                         * Request to peek for EXPIND in
                                         * rcvbuf.
                                         */
                                        if (ti_expind_on_rdqueues(q)) {
                                                /*
                                                 * Expedited data is
                                                 * queued on the stream
                                                 * read side
                                                 */
                                                tsap->tsa_flags |=
                                                    TSAF_EXP_QUEUED;
                                        }
                                        tp->tim_flags &=
                                            ~PEEK_RDQ_EXPIND;
                                }
                                mp->b_wptr += 2*sizeof (uint32_t);
                        }
                        tim_ioctl_send_reply(q, tp->tim_iocsave, mp);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(WAITIOCACK | WAIT_IOCINFOACK |
                            TI_CAP_RECVD | CAP_WANTS_INFO);
                        break;
                }
            }

            putnext(q, mp);
            break;

            case T_ADDR_ACK:
                tilog("timodrproc: Got T_ADDR_ACK\n", 0);
                tim_send_reply(q, mp, tp, T_ADDR_REQ);
                break;

                case T_CONN_IND: {
                        struct T_conn_ind *tcip =
                            (struct T_conn_ind *)mp->b_rptr;

                        tilog("timodrproc: Got T_CONN_IND\n", 0);

                        if (blen >= sizeof (*tcip) &&
                            MBLKIN(mp, tcip->SRC_offset, tcip->SRC_length)) {
                                if (((nbp = dupmsg(mp)) != NULL) ||
                                    ((nbp = copymsg(mp)) != NULL)) {
                                        nbp->b_next = tp->tim_consave;
                                        tp->tim_consave = nbp;
                                } else {
                                        tim_recover(q, mp,
                                            (t_scalar_t)sizeof (mblk_t));
                                        return (1);
                                }
                        }
                        if (auditing)
                                audit_sock(T_CONN_IND, q, mp, TIMOD_ID);
                        putnext(q, mp);
                        break;
                }

            case T_CONN_CON:
                mutex_enter(&tp->tim_mutex);
                if (tp->tim_peercred != NULL)
                        crfree(tp->tim_peercred);
                tp->tim_peercred = msg_getcred(mp, &tp->tim_cpid);
                if (tp->tim_peercred != NULL)
                        crhold(tp->tim_peercred);
                mutex_exit(&tp->tim_mutex);

                tilog("timodrproc: Got T_CONN_CON\n", 0);

                tp->tim_flags &= ~CONNWAIT;
                putnext(q, mp);
                break;

            case T_DISCON_IND: {
                struct T_discon_ind *disp;
                struct T_conn_ind *conp;
                mblk_t *pbp = NULL;

                if (q->q_first != 0)
                        tilog("timodrput: T_DISCON_IND - flow control\n", 0);

                if (blen < sizeof (*disp)) {
                        putnext(q, mp);
                        break;
                }

                disp = (struct T_discon_ind *)mp->b_rptr;

                tilog("timodrproc: Got T_DISCON_IND Reason: %d\n",
                    disp->DISCON_reason);

                tp->tim_flags &= ~(CONNWAIT|LOCORDREL|REMORDREL);
                tim_clear_peer(tp);
                for (nbp = tp->tim_consave; nbp; nbp = nbp->b_next) {
                        conp = (struct T_conn_ind *)nbp->b_rptr;
                        if (conp->SEQ_number == disp->SEQ_number)
                                break;
                        pbp = nbp;
                }
                if (nbp) {
                        if (pbp)
                                pbp->b_next = nbp->b_next;
                        else
                                tp->tim_consave = nbp->b_next;
                        nbp->b_next = NULL;
                        freemsg(nbp);
                }
                putnext(q, mp);
                break;
            }

            case T_ORDREL_IND:

                    tilog("timodrproc: Got T_ORDREL_IND\n", 0);

                    if (tp->tim_flags & LOCORDREL) {
                            tp->tim_flags &= ~(LOCORDREL|REMORDREL);
                            tim_clear_peer(tp);
                    } else {
                            tp->tim_flags |= REMORDREL;
                    }
                    putnext(q, mp);
                    break;

            case T_EXDATA_IND:
            case T_DATA_IND:
            case T_UNITDATA_IND:
                if (pptr->type == T_EXDATA_IND)
                        tilog("timodrproc: Got T_EXDATA_IND\n", 0);

                if (!bcanputnext(q, mp->b_band)) {
                        (void) putbq(q, mp);
                        return (1);
                }
                putnext(q, mp);
                break;

            case T_CAPABILITY_ACK: {
                        struct T_capability_ack *tca;

                        if (blen < sizeof (*tca)) {
                                putnext(q, mp);
                                break;
                        }

                        /* This transport supports T_CAPABILITY_REQ */
                        tilog("timodrproc: Got T_CAPABILITY_ACK\n", 0);

                        PI_PROVLOCK(tp->tim_provinfo);
                        if (tp->tim_provinfo->tpi_capability != PI_YES)
                                tp->tim_provinfo->tpi_capability = PI_YES;
                        PI_PROVUNLOCK(tp->tim_provinfo);

                        /* Reset possible pending timeout */
                        if (tp->tim_tcap_timoutid != 0) {
                                (void) quntimeout(q, tp->tim_tcap_timoutid);
                                tp->tim_tcap_timoutid = 0;
                        }

                        tca = (struct T_capability_ack *)mp->b_rptr;

                        if (tca->CAP_bits1 & TC1_INFO)
                                timodprocessinfo(q, tp, &tca->INFO_ack);

                        tim_send_reply(q, mp, tp, T_CAPABILITY_REQ);
                }
                break;
            }
            break;

        case M_FLUSH:

                tilog("timodrproc: Got M_FLUSH\n", 0);

                if (*mp->b_rptr & FLUSHR) {
                        if (*mp->b_rptr & FLUSHBAND)
                                flushband(q, *(mp->b_rptr + 1), FLUSHDATA);
                        else
                                flushq(q, FLUSHDATA);
                }
                putnext(q, mp);
                break;

        case M_IOCACK:
            iocbp = (struct iocblk *)mp->b_rptr;

            tilog("timodrproc: Got M_IOCACK\n", 0);

            if (iocbp->ioc_cmd == TI_GETMYNAME) {

                /*
                 * Transport provider supports this ioctl,
                 * so I don't have to.
                 */
                if ((tp->tim_flags & DO_MYNAME) != 0) {
                        tp->tim_flags &= ~DO_MYNAME;
                        PI_PROVLOCK(tp->tim_provinfo);
                        tp->tim_provinfo->tpi_myname = PI_YES;
                        PI_PROVUNLOCK(tp->tim_provinfo);
                }

                ASSERT(tp->tim_mymaxlen >= 0);
                if (tp->tim_mymaxlen != 0) {
                        kmem_free(tp->tim_myname, (size_t)tp->tim_mymaxlen);
                        tp->tim_myname = NULL;
                        tp->tim_mymaxlen = 0;
                }
                /* tim_iocsave may already be overwritten. */
                if (tp->tim_saved_prim == -1) {
                        freemsg(tp->tim_iocsave);
                        tp->tim_iocsave = NULL;
                }
            } else if (iocbp->ioc_cmd == TI_GETPEERNAME) {
                boolean_t clearit;

                /*
                 * Transport provider supports this ioctl,
                 * so I don't have to.
                 */
                if ((tp->tim_flags & DO_PEERNAME) != 0) {
                        tp->tim_flags &= ~DO_PEERNAME;
                        PI_PROVLOCK(tp->tim_provinfo);
                        tp->tim_provinfo->tpi_peername = PI_YES;
                        PI_PROVUNLOCK(tp->tim_provinfo);
                }

                mutex_enter(&tp->tim_mutex);
                ASSERT(tp->tim_peermaxlen >= 0);
                clearit = tp->tim_peermaxlen != 0;
                if (clearit) {
                        kmem_free(tp->tim_peername, tp->tim_peermaxlen);
                        tp->tim_peername = NULL;
                        tp->tim_peermaxlen = 0;
                        tp->tim_peerlen = 0;
                }
                mutex_exit(&tp->tim_mutex);
                if (clearit) {
                        mblk_t *bp;

                        bp = tp->tim_consave;
                        while (bp != NULL) {
                                nbp = bp->b_next;
                                bp->b_next = NULL;
                                freemsg(bp);
                                bp = nbp;
                        }
                        tp->tim_consave = NULL;
                }
                /* tim_iocsave may already be overwritten. */
                if (tp->tim_saved_prim == -1) {
                        freemsg(tp->tim_iocsave);
                        tp->tim_iocsave = NULL;
                }
            }
            putnext(q, mp);
            break;

        case M_IOCNAK:

                tilog("timodrproc: Got M_IOCNAK\n", 0);

                iocbp = (struct iocblk *)mp->b_rptr;
                if (((iocbp->ioc_cmd == TI_GETMYNAME) ||
                    (iocbp->ioc_cmd == TI_GETPEERNAME)) &&
                    ((iocbp->ioc_error == EINVAL) || (iocbp->ioc_error == 0))) {
                        PI_PROVLOCK(tp->tim_provinfo);
                        if (iocbp->ioc_cmd == TI_GETMYNAME) {
                                if (tp->tim_provinfo->tpi_myname == PI_DONTKNOW)
                                        tp->tim_provinfo->tpi_myname = PI_NO;
                        } else if (iocbp->ioc_cmd == TI_GETPEERNAME) {
                                if (tp->tim_provinfo->tpi_peername == PI_DONTKNOW)
                                        tp->tim_provinfo->tpi_peername = PI_NO;
                        }
                        PI_PROVUNLOCK(tp->tim_provinfo);
                        /* tim_iocsave may already be overwritten. */
                        if ((tp->tim_iocsave != NULL) &&
                            (tp->tim_saved_prim == -1)) {
                                freemsg(mp);
                                mp = tp->tim_iocsave;
                                tp->tim_iocsave = NULL;
                                tp->tim_flags |= NAMEPROC;
                                if (ti_doname(WR(q), mp) != DONAME_CONT) {
                                        tp->tim_flags &= ~NAMEPROC;
                                }
                                break;
                        }
                }
                putnext(q, mp);
                break;
        }

        return (0);
}

/*
 * timodwput -  Module write put procedure.  This is called from
 *              the module, driver, or stream head upstream/downstream.
 *              Handles M_FLUSH, M_DATA and some M_PROTO (T_DATA_REQ,
 *              and T_UNITDATA_REQ) messages. All others are queued to
 *              be handled by the service procedures.
 */

static int
timodwput(queue_t *q, mblk_t *mp)
{
        union T_primitives *pptr;
        struct tim_tim *tp;
        struct iocblk *iocbp;

        /*
         * Enqueue normal-priority messages if our queue already
         * holds some messages for deferred processing but don't
         * enqueue those M_IOCTLs which will result in an
         * M_PCPROTO (ie, high priority) message being created.
         */
        if (q->q_first != 0 && mp->b_datap->db_type < QPCTL) {
                if (mp->b_datap->db_type == M_IOCTL) {
                        iocbp = (struct iocblk *)mp->b_rptr;
                        switch (iocbp->ioc_cmd) {
                        default:
                                (void) putq(q, mp);
                                return (0);

                        case TI_GETINFO:
                        case TI_SYNC:
                        case TI_CAPABILITY:
                                break;
                        }
                } else {
                        (void) putq(q, mp);
                        return (0);
                }
        }
        /*
         * Inline processing of data (to avoid additional procedure call).
         * Rest is handled in timodwproc.
         */

        switch (mp->b_datap->db_type) {
        case M_DATA:
                tp = (struct tim_tim *)q->q_ptr;
                ASSERT(tp);
                if (tp->tim_flags & CLTS) {
                        mblk_t  *tmp;

                        if ((tmp = tim_filladdr(q, mp, B_FALSE)) == NULL) {
                                (void) putq(q, mp);
                                break;
                        } else {
                                mp = tmp;
                        }
                }
                if (bcanputnext(q, mp->b_band))
                        putnext(q, mp);
                else
                        (void) putq(q, mp);
                break;
        case M_PROTO:
        case M_PCPROTO:
                pptr = (union T_primitives *)mp->b_rptr;
                switch (pptr->type) {
                case T_UNITDATA_REQ:
                        tp = (struct tim_tim *)q->q_ptr;
                        ASSERT(tp);
                        if (tp->tim_flags & CLTS) {
                                mblk_t  *tmp;

                                tmp = tim_filladdr(q, mp, B_FALSE);
                                if (tmp == NULL) {
                                        (void) putq(q, mp);
                                        break;
                                } else {
                                        mp = tmp;
                                }
                        }
                        if (bcanputnext(q, mp->b_band))
                                putnext(q, mp);
                        else
                                (void) putq(q, mp);
                        break;

                case T_DATA_REQ:
                case T_EXDATA_REQ:
                        if (bcanputnext(q, mp->b_band))
                                putnext(q, mp);
                        else
                                (void) putq(q, mp);
                        break;
                default:
                        (void) timodwproc(q, mp);
                        break;
                }
                break;
        default:
                (void) timodwproc(q, mp);
                break;
        }
        return (0);
}
/*
 * timodwsrv -  Module write queue service procedure.
 *              This is called when messages are placed on an empty queue,
 *              when high priority messages are placed on the queue, and
 *              when flow control restrictions subside.  This code used to
 *              be included in a put procedure, but it was moved to a
 *              service procedure because several points were added where
 *              memory allocation could fail, and there is no reasonable
 *              recovery mechanism from the put procedure.
 */
static int
timodwsrv(queue_t *q)
{
        mblk_t *mp;

        ASSERT(q != NULL);
        if (q->q_ptr == NULL)
                return (0);

        while ((mp = getq(q)) != NULL) {
                if (timodwproc(q, mp)) {
                        /*
                         * timodwproc did a putbq - stop processing
                         * messages.
                         */
                        return (0);
                }
        }
        return (0);
}

/*
 * Common routine to process write side messages
 */

static int
timodwproc(queue_t *q, mblk_t *mp)
{
        union T_primitives *pptr;
        struct tim_tim *tp;
        uint32_t auditing = AU_AUDITING();
        mblk_t *tmp;
        struct iocblk *iocbp;
        int error;

        tp = (struct tim_tim *)q->q_ptr;

        switch (mp->b_datap->db_type) {
        default:
                putnext(q, mp);
                break;

        case M_DATA:
                if (tp->tim_flags & CLTS) {
                        if ((tmp = tim_filladdr(q, mp, B_TRUE)) == NULL) {
                                return (1);
                        } else {
                                mp = tmp;
                        }
                }
                if (!bcanputnext(q, mp->b_band)) {
                        (void) putbq(q, mp);
                        return (1);
                }
                putnext(q, mp);
                break;

        case M_IOCTL:

                iocbp = (struct iocblk *)mp->b_rptr;
                TILOG("timodwproc: Got M_IOCTL(%d)\n", iocbp->ioc_cmd);

                ASSERT(MBLKL(mp) == sizeof (struct iocblk));

                /*
                 * TPI requires we await response to a previously sent message
                 * before handling another, put it back on the head of queue.
                 * Since putbq() may see QWANTR unset when called from the
                 * service procedure, the queue must be explicitly scheduled
                 * for service, as no backenable will occur for this case.
                 * tim_ioctl_retry() sets a timer to handle the qenable.
                 */
                if (tp->tim_flags & WAITIOCACK) {
                        TILOG("timodwproc: putbq M_IOCTL(%d)\n",
                            iocbp->ioc_cmd);
                        (void) putbq(q, mp);
                        /* Called from timodwsrv() and messages on queue */
                        if (!(q->q_flag & QWANTR))
                                tim_ioctl_retry(q);
                        return (1);
                }

                switch (iocbp->ioc_cmd) {
                default:
                        putnext(q, mp);
                        break;

                case _I_GETPEERCRED:
                        if ((tp->tim_flags & COTS) == 0) {
                                miocnak(q, mp, 0, ENOTSUP);
                        } else {
                                mblk_t *cmp = mp->b_cont;
                                k_peercred_t *kp = NULL;

                                mutex_enter(&tp->tim_mutex);
                                if (cmp != NULL &&
                                    iocbp->ioc_flag == IOC_NATIVE &&
                                    (tp->tim_flags &
                                    (CONNWAIT|LOCORDREL|REMORDREL)) == 0 &&
                                    tp->tim_peercred != NULL &&
                                    DB_TYPE(cmp) == M_DATA &&
                                    MBLKL(cmp) == sizeof (k_peercred_t)) {
                                        kp = (k_peercred_t *)cmp->b_rptr;
                                        crhold(kp->pc_cr = tp->tim_peercred);
                                        kp->pc_cpid = tp->tim_cpid;
                                }
                                mutex_exit(&tp->tim_mutex);
                                if (kp != NULL)
                                        miocack(q, mp, sizeof (*kp), 0);
                                else
                                        miocnak(q, mp, 0, ENOTCONN);
                        }
                        break;
                case TI_BIND:
                case TI_UNBIND:
                case TI_OPTMGMT:
                case TI_GETADDRS:
                        TILOG("timodwproc: TI_{BIND|UNBIND|OPTMGMT|GETADDRS}"
                            "\n", 0);

                        /*
                         * We know that tim_send_ioctl_tpi_msg() is only
                         * going to examine the `type' field, so we only
                         * check that we can access that much data.
                         */
                        error = miocpullup(mp, sizeof (t_scalar_t));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }
                        tim_send_ioctl_tpi_msg(q, mp, tp, iocbp);
                        break;

                case TI_GETINFO:
                        TILOG("timodwproc: TI_GETINFO\n", 0);
                        error = miocpullup(mp, sizeof (struct T_info_req));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }
                        tp->tim_flags |= WAIT_IOCINFOACK;
                        tim_send_ioctl_tpi_msg(q, mp, tp, iocbp);
                        break;

                case TI_SYNC: {
                        mblk_t *tsr_mp;
                        struct ti_sync_req *tsr;
                        uint32_t tsr_flags;

                        error = miocpullup(mp, sizeof (struct ti_sync_req));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }

                        tsr_mp = mp->b_cont;
                        tsr = (struct ti_sync_req *)tsr_mp->b_rptr;
                        TILOG("timodwproc: TI_SYNC(%x)\n", tsr->tsr_flags);

                        /*
                         * Save out the value of tsr_flags, in case we
                         * reallocb() tsr_mp (below).
                         */
                        tsr_flags = tsr->tsr_flags;
                        if ((tsr_flags & TSRF_INFO_REQ) == 0) {
                                mblk_t *ack_mp = reallocb(tsr_mp,
                                    sizeof (struct ti_sync_ack), 0);

                                /* Can reply immediately. */
                                mp->b_cont = NULL;
                                if (ack_mp == NULL) {
                                        tilog("timodwproc: allocb failed no "
                                            "recovery attempt\n", 0);
                                        freemsg(tsr_mp);
                                        miocnak(q, mp, 0, ENOMEM);
                                } else {
                                        tim_answer_ti_sync(q, mp, tp,
                                            ack_mp, tsr_flags);
                                }
                                break;
                        }

                        /*
                         * This code is retained for compatibility with
                         * old statically linked applications. New code
                         * should use TI_CAPABILITY for all TPI
                         * information and should not use TSRF_INFO_REQ
                         * flag.
                         *
                         * defer processsing necessary to rput procedure
                         * as we need to get information from transport
                         * driver. Set flags that will tell the read
                         * side the work needed on this request.
                         */

                        if (tsr_flags & TSRF_IS_EXP_IN_RCVBUF)
                                tp->tim_flags |= PEEK_RDQ_EXPIND;

                        /*
                         * Convert message to a T_INFO_REQ message; relies
                         * on sizeof (struct ti_sync_req) >= sizeof (struct
                         * T_info_req)).
                         */
                        ASSERT(MBLKL(tsr_mp) >= sizeof (struct T_info_req));

                        ((struct T_info_req *)tsr_mp->b_rptr)->PRIM_type =
                            T_INFO_REQ;
                        tsr_mp->b_wptr = tsr_mp->b_rptr +
                            sizeof (struct T_info_req);
                        tp->tim_flags |= WAIT_IOCINFOACK;
                        tim_send_ioctl_tpi_msg(q, mp, tp, iocbp);
                }
                break;

                case TI_CAPABILITY: {
                        mblk_t *tcsr_mp;
                        struct T_capability_req *tcr;

                        error = miocpullup(mp, sizeof (*tcr));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }

                        tcsr_mp = mp->b_cont;
                        tcr = (struct T_capability_req *)tcsr_mp->b_rptr;
                        TILOG("timodwproc: TI_CAPABILITY(CAP_bits1 = %x)\n",
                            tcr->CAP_bits1);

                        if (tcr->PRIM_type != T_CAPABILITY_REQ) {
                                TILOG("timodwproc: invalid msg type %d\n",
                                    tcr->PRIM_type);
                                miocnak(q, mp, 0, EPROTO);
                                break;
                        }

                        switch (tp->tim_provinfo->tpi_capability) {
                        case PI_YES:
                                /* Just send T_CAPABILITY_REQ down */
                                tim_send_ioctl_tpi_msg(q, mp, tp, iocbp);
                                break;

                        case PI_DONTKNOW:
                                /*
                                 * It is unknown yet whether transport provides
                                 * T_CAPABILITY_REQ or not. Send message down
                                 * and wait for reply.
                                 */

                                ASSERT(tp->tim_tcap_timoutid == 0);
                                if ((tcr->CAP_bits1 & TC1_INFO) == 0) {
                                        tp->tim_flags |= TI_CAP_RECVD;
                                } else {
                                        tp->tim_flags |= (TI_CAP_RECVD |
                                            CAP_WANTS_INFO);
                                }

                                tp->tim_tcap_timoutid = qtimeout(q,
                                    tim_tcap_timer, q, tim_tcap_wait * hz);
                                tim_send_ioctl_tpi_msg(q, mp, tp, iocbp);
                                break;

                        case PI_NO:
                                /*
                                 * Transport doesn't support T_CAPABILITY_REQ.
                                 * Either reply immediately or send T_INFO_REQ
                                 * if needed.
                                 */
                                if ((tcr->CAP_bits1 & TC1_INFO) != 0) {
                                        tp->tim_flags |= (TI_CAP_RECVD |
                                            CAP_WANTS_INFO | WAIT_IOCINFOACK);
                                        TILOG("timodwproc: sending down "
                                            "T_INFO_REQ, flags = %x\n",
                                            tp->tim_flags);

                                /*
                                 * Generate T_INFO_REQ message and send
                                 * it down
                                 */
                                        ((struct T_info_req *)tcsr_mp->b_rptr)->
                                            PRIM_type = T_INFO_REQ;
                                        tcsr_mp->b_wptr = tcsr_mp->b_rptr +
                                            sizeof (struct T_info_req);
                                        tim_send_ioctl_tpi_msg(q, mp, tp,
                                            iocbp);
                                        break;
                                }


                                /*
                                 * Can reply immediately. Just send back
                                 * T_CAPABILITY_ACK with CAP_bits1 set to 0.
                                 */
                                mp->b_cont = tcsr_mp = tpi_ack_alloc(mp->b_cont,
                                    sizeof (struct T_capability_ack), M_PCPROTO,
                                    T_CAPABILITY_ACK);

                                if (tcsr_mp == NULL) {
                                        tilog("timodwproc: allocb failed no "
                                            "recovery attempt\n", 0);
                                        miocnak(q, mp, 0, ENOMEM);
                                        break;
                                }

                                tp->tim_flags &= ~(WAITIOCACK | TI_CAP_RECVD |
                                    WAIT_IOCINFOACK | CAP_WANTS_INFO);
                                ((struct T_capability_ack *)
                                    tcsr_mp->b_rptr)->CAP_bits1 = 0;
                                tim_ioctl_send_reply(q, mp, tcsr_mp);

                                /*
                                 * It could happen when timod is awaiting ack
                                 * for TI_GETPEERNAME/TI_GETMYNAME.
                                 */
                                if (tp->tim_iocsave != NULL) {
                                        freemsg(tp->tim_iocsave);
                                        tp->tim_iocsave = NULL;
                                        tp->tim_saved_prim = -1;
                                }
                                break;

                        default:
                                cmn_err(CE_PANIC,
                                    "timodwproc: unknown tpi_capability value "
                                    "%d\n", tp->tim_provinfo->tpi_capability);
                                break;
                        }
                }
                break;

                case TI_GETMYNAME:

                        tilog("timodwproc: Got TI_GETMYNAME\n", 0);

                        if (tp->tim_provinfo->tpi_myname == PI_YES) {
                                putnext(q, mp);
                                break;
                        }
                        goto getname;

                case TI_GETPEERNAME:

                        tilog("timodwproc: Got TI_GETPEERNAME\n", 0);

                        if (tp->tim_provinfo->tpi_peername == PI_YES) {
                                putnext(q, mp);
                                break;
                        }
getname:
                        if ((tmp = copymsg(mp)) == NULL) {
                                tim_recover(q, mp, msgsize(mp));
                                return (1);
                        }
                        /*
                         * tim_iocsave may be non-NULL when timod is awaiting
                         * ack for another TI_GETPEERNAME/TI_GETMYNAME.
                         */
                        freemsg(tp->tim_iocsave);
                        tp->tim_iocsave = mp;
                        tp->tim_saved_prim = -1;
                        putnext(q, tmp);
                        break;
                        }
                break;

        case M_IOCDATA:

                if (tp->tim_flags & NAMEPROC) {
                        if (ti_doname(q, mp) != DONAME_CONT) {
                                tp->tim_flags &= ~NAMEPROC;
                        }
                } else
                        putnext(q, mp);
                break;

        case M_PROTO:
        case M_PCPROTO:
                if (MBLKL(mp) < sizeof (t_scalar_t)) {
                        merror(q, mp, EPROTO);
                        return (1);
                }

                pptr = (union T_primitives *)mp->b_rptr;
                switch (pptr->type) {
                default:
                        putnext(q, mp);
                        break;

                case T_EXDATA_REQ:
                case T_DATA_REQ:
                        if (pptr->type == T_EXDATA_REQ)
                                tilog("timodwproc: Got T_EXDATA_REQ\n", 0);

                if (!bcanputnext(q, mp->b_band)) {
                        (void) putbq(q, mp);
                        return (1);
                }
                putnext(q, mp);
                break;

                case T_UNITDATA_REQ:
                        if (tp->tim_flags & CLTS) {
                                tmp = tim_filladdr(q, mp, B_TRUE);
                                if (tmp == NULL) {
                                        return (1);
                                } else {
                                        mp = tmp;
                                }
                        }
                        if (auditing)
                                audit_sock(T_UNITDATA_REQ, q, mp, TIMOD_ID);
                if (!bcanputnext(q, mp->b_band)) {
                                (void) putbq(q, mp);
                                return (1);
                        }
                        putnext(q, mp);
                        break;

                case T_CONN_REQ: {
                        struct T_conn_req *reqp = (struct T_conn_req *)
                            mp->b_rptr;
                        void *p;

                        tilog("timodwproc: Got T_CONN_REQ\n", 0);

                        if (MBLKL(mp) < sizeof (struct T_conn_req)) {
                                merror(q, mp, EPROTO);
                                return (1);
                        }

                        if (tp->tim_flags & DO_PEERNAME) {
                                if (!MBLKIN(mp, reqp->DEST_offset,
                                    reqp->DEST_length)) {
                                        merror(q, mp, EPROTO);
                                        return (1);
                                }
                                ASSERT(reqp->DEST_length >= 0);
                                mutex_enter(&tp->tim_mutex);
                                if (reqp->DEST_length > tp->tim_peermaxlen) {
                                        p = kmem_alloc(reqp->DEST_length,
                                            KM_NOSLEEP);
                                        if (p == NULL) {
                                                mutex_exit(&tp->tim_mutex);
                                                tilog("timodwproc: kmem_alloc "
                                                    "failed, attempting "
                                                    "recovery\n", 0);
                                                tim_recover(q, mp,
                                                    reqp->DEST_length);
                                                return (1);
                                        }
                                        if (tp->tim_peermaxlen)
                                                kmem_free(tp->tim_peername,
                                                    tp->tim_peermaxlen);
                                        tp->tim_peername = p;
                                        tp->tim_peermaxlen = reqp->DEST_length;
                                }
                                tp->tim_peerlen = reqp->DEST_length;
                                p = mp->b_rptr + reqp->DEST_offset;
                                bcopy(p, tp->tim_peername, tp->tim_peerlen);
                                mutex_exit(&tp->tim_mutex);
                        }
                        if (tp->tim_flags & COTS)
                                tp->tim_flags |= CONNWAIT;
                        if (auditing)
                                audit_sock(T_CONN_REQ, q, mp, TIMOD_ID);
                putnext(q, mp);
                break;
                }

                case O_T_CONN_RES:
                case T_CONN_RES: {
                        struct T_conn_res *resp;
                        struct T_conn_ind *indp;
                        mblk_t *pmp = NULL;
                        mblk_t *nbp;

                        if (MBLKL(mp) < sizeof (struct T_conn_res) ||
                            (tp->tim_flags & WAITIOCACK)) {
                                merror(q, mp, EPROTO);
                                return (1);
                        }

                        resp = (struct T_conn_res *)mp->b_rptr;
                        for (tmp = tp->tim_consave; tmp != NULL;
                            tmp = tmp->b_next) {
                                indp = (struct T_conn_ind *)tmp->b_rptr;
                                if (indp->SEQ_number == resp->SEQ_number)
                                        break;
                                pmp = tmp;
                        }
                        if (tmp == NULL)
                                goto cresout;

                        if ((nbp = dupb(mp)) == NULL &&
                            (nbp = copyb(mp)) == NULL) {
                                tim_recover(q, mp, msgsize(mp));
                                return (1);
                        }

                        if (pmp != NULL)
                                pmp->b_next = tmp->b_next;
                        else
                                tp->tim_consave = tmp->b_next;
                        tmp->b_next = NULL;

                        /*
                         * Construct a list with:
                         *      nbp - copy of user's original request
                         *      tmp - the extracted T_conn_ind
                         */
                        nbp->b_cont = tmp;
                        /*
                         * tim_iocsave may be non-NULL when timod is awaiting
                         * ack for TI_GETPEERNAME/TI_GETMYNAME.
                         */
                        freemsg(tp->tim_iocsave);
                        tp->tim_iocsave = nbp;
                        tp->tim_saved_prim = pptr->type;
                        tp->tim_flags |= WAIT_CONNRESACK | WAITIOCACK;

                cresout:
                        putnext(q, mp);
                        break;
                }

                case T_DISCON_REQ: {
                        struct T_discon_req *disp;
                        struct T_conn_ind *conp;
                        mblk_t *pmp = NULL;

                        if (MBLKL(mp) < sizeof (struct T_discon_req)) {
                                merror(q, mp, EPROTO);
                                return (1);
                        }

                        disp = (struct T_discon_req *)mp->b_rptr;
                        tp->tim_flags &= ~(CONNWAIT|LOCORDREL|REMORDREL);
                        tim_clear_peer(tp);

                        /*
                         * If we are already connected, there won't
                         * be any messages on tim_consave.
                         */
                        for (tmp = tp->tim_consave; tmp; tmp = tmp->b_next) {
                                conp = (struct T_conn_ind *)tmp->b_rptr;
                                if (conp->SEQ_number == disp->SEQ_number)
                                        break;
                                pmp = tmp;
                        }
                        if (tmp) {
                                if (pmp)
                                        pmp->b_next = tmp->b_next;
                                else
                                        tp->tim_consave = tmp->b_next;
                                tmp->b_next = NULL;
                                freemsg(tmp);
                        }
                        putnext(q, mp);
                        break;
                }

                case T_ORDREL_REQ:
                        if (tp->tim_flags & REMORDREL) {
                                tp->tim_flags &= ~(LOCORDREL|REMORDREL);
                                tim_clear_peer(tp);
                        } else {
                                tp->tim_flags |= LOCORDREL;
                        }
                        putnext(q, mp);
                        break;

                case T_CAPABILITY_REQ:
                        tilog("timodwproc: Got T_CAPABILITY_REQ\n", 0);
                        /*
                         * XXX: We may know at this point whether transport
                         * provides T_CAPABILITY_REQ or not and we may utilise
                         * this knowledge here.
                         */
                        putnext(q, mp);
                        break;
                }
                break;
        case M_FLUSH:

                tilog("timodwproc: Got M_FLUSH\n", 0);

                if (*mp->b_rptr & FLUSHW) {
                        if (*mp->b_rptr & FLUSHBAND)
                                flushband(q, *(mp->b_rptr + 1), FLUSHDATA);
                        else
                                flushq(q, FLUSHDATA);
                }
                putnext(q, mp);
                break;
        }

        return (0);
}

static void
tilog(char *str, t_scalar_t arg)
{
        if (dotilog) {
                if (dotilog & 2)
                        cmn_err(CE_CONT, str, arg);
                if (dotilog & 4)
                        (void) strlog(TIMOD_ID, -1, 0, SL_TRACE | SL_ERROR,
                            str, arg);
                else
                        (void) strlog(TIMOD_ID, -1, 0, SL_TRACE, str, arg);
        }
}

static void
tilogp(char *str, uintptr_t arg)
{
        if (dotilog) {
                if (dotilog & 2)
                        cmn_err(CE_CONT, str, arg);
                if (dotilog & 4)
                        (void) strlog(TIMOD_ID, -1, 0, SL_TRACE | SL_ERROR,
                            str, arg);
                else
                        (void) strlog(TIMOD_ID, -1, 0, SL_TRACE, str, arg);
        }
}


/*
 * Process the TI_GETNAME ioctl.  If no name exists, return len = 0
 * in strbuf structures.  The state transitions are determined by what
 * is hung of cq_private (cp_private) in the copyresp (copyreq) structure.
 * The high-level steps in the ioctl processing are as follows:
 *
 * 1) we recieve an transparent M_IOCTL with the arg in the second message
 *      block of the message.
 * 2) we send up an M_COPYIN request for the strbuf structure pointed to
 *      by arg.  The block containing arg is hung off cq_private.
 * 3) we receive an M_IOCDATA response with cp->cp_private->b_cont == NULL.
 *      This means that the strbuf structure is found in the message block
 *      mp->b_cont.
 * 4) we send up an M_COPYOUT request with the strbuf message hung off
 *      cq_private->b_cont.  The address we are copying to is strbuf.buf.
 *      we set strbuf.len to 0 to indicate that we should copy the strbuf
 *      structure the next time.  The message mp->b_cont contains the
 *      address info.
 * 5) we receive an M_IOCDATA with cp_private->b_cont != NULL and
 *      strbuf.len == 0.  Restore strbuf.len to either tp->tim_mylen or
 *      tp->tim_peerlen.
 * 6) we send up an M_COPYOUT request with a copy of the strbuf message
 *      hung off mp->b_cont.  In the strbuf structure in the message hung
 *      off cq_private->b_cont, we set strbuf.len to 0 and strbuf.maxlen
 *      to 0.  This means that the next step is to ACK the ioctl.
 * 7) we receive an M_IOCDATA message with cp_private->b_cont != NULL and
 *      strbuf.len == 0 and strbuf.maxlen == 0.  Free up cp->private and
 *      send an M_IOCACK upstream, and we are done.
 *
 */
static int
ti_doname(
        queue_t *q,             /* queue message arrived at */
        mblk_t *mp)             /* M_IOCTL or M_IOCDATA message only */
{
        struct iocblk *iocp;
        struct copyreq *cqp;
        STRUCT_HANDLE(strbuf, sb);
        struct copyresp *csp;
        int ret;
        mblk_t *bp;
        struct tim_tim *tp = q->q_ptr;
        boolean_t getpeer;

        switch (mp->b_datap->db_type) {
        case M_IOCTL:
                iocp = (struct iocblk *)mp->b_rptr;
                if ((iocp->ioc_cmd != TI_GETMYNAME) &&
                    (iocp->ioc_cmd != TI_GETPEERNAME)) {
                        tilog("ti_doname: bad M_IOCTL command\n", 0);
                        miocnak(q, mp, 0, EINVAL);
                        ret = DONAME_FAIL;
                        break;
                }
                if ((iocp->ioc_count != TRANSPARENT)) {
                        miocnak(q, mp, 0, EINVAL);
                        ret = DONAME_FAIL;
                        break;
                }

                cqp = (struct copyreq *)mp->b_rptr;
                cqp->cq_private = mp->b_cont;
                cqp->cq_addr = (caddr_t)*(intptr_t *)mp->b_cont->b_rptr;
                mp->b_cont = NULL;
                cqp->cq_size = SIZEOF_STRUCT(strbuf, iocp->ioc_flag);
                cqp->cq_flag = 0;
                mp->b_datap->db_type = M_COPYIN;
                mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
                qreply(q, mp);
                ret = DONAME_CONT;
                break;

        case M_IOCDATA:
                csp = (struct copyresp *)mp->b_rptr;
                iocp = (struct iocblk *)mp->b_rptr;
                cqp = (struct copyreq *)mp->b_rptr;
                if ((csp->cp_cmd != TI_GETMYNAME) &&
                    (csp->cp_cmd != TI_GETPEERNAME)) {
                        cmn_err(CE_WARN, "ti_doname: bad M_IOCDATA command\n");
                        miocnak(q, mp, 0, EINVAL);
                        ret = DONAME_FAIL;
                        break;
                }
                if (csp->cp_rval) {     /* error */
                        freemsg(csp->cp_private);
                        freemsg(mp);
                        ret = DONAME_FAIL;
                        break;
                }
                ASSERT(csp->cp_private != NULL);
                getpeer = csp->cp_cmd == TI_GETPEERNAME;
                if (getpeer)
                        mutex_enter(&tp->tim_mutex);
                if (csp->cp_private->b_cont == NULL) {  /* got strbuf */
                        ASSERT(mp->b_cont);
                        STRUCT_SET_HANDLE(sb, iocp->ioc_flag,
                            (void *)mp->b_cont->b_rptr);
                        if (getpeer) {
                                if (tp->tim_peerlen == 0) {
                                        /* copy just strbuf */
                                        STRUCT_FSET(sb, len, 0);
                                } else if (tp->tim_peerlen >
                                    STRUCT_FGET(sb, maxlen)) {
                                        mutex_exit(&tp->tim_mutex);
                                        miocnak(q, mp, 0, ENAMETOOLONG);
                                        ret = DONAME_FAIL;
                                        break;
                                } else {
                                        /* copy buffer */
                                        STRUCT_FSET(sb, len, tp->tim_peerlen);
                                }
                        } else {
                                if (tp->tim_mylen == 0) {
                                        /* copy just strbuf */
                                        STRUCT_FSET(sb, len, 0);
                                } else if (tp->tim_mylen >
                                    STRUCT_FGET(sb, maxlen)) {
                                        freemsg(csp->cp_private);
                                        miocnak(q, mp, 0, ENAMETOOLONG);
                                        ret = DONAME_FAIL;
                                        break;
                                } else {
                                        /* copy buffer */
                                        STRUCT_FSET(sb, len, tp->tim_mylen);
                                }
                        }
                        csp->cp_private->b_cont = mp->b_cont;
                        mp->b_cont = NULL;
                }
                STRUCT_SET_HANDLE(sb, iocp->ioc_flag,
                    (void *)csp->cp_private->b_cont->b_rptr);
                if (STRUCT_FGET(sb, len) == 0) {
                        /*
                         * restore strbuf.len
                         */
                        if (getpeer)
                                STRUCT_FSET(sb, len, tp->tim_peerlen);
                        else
                                STRUCT_FSET(sb, len, tp->tim_mylen);

                        if (getpeer)
                                mutex_exit(&tp->tim_mutex);
                        if (STRUCT_FGET(sb, maxlen) == 0) {

                                /*
                                 * ack the ioctl
                                 */
                                freemsg(csp->cp_private);
                                tim_ioctl_send_reply(q, mp, NULL);
                                ret = DONAME_DONE;
                                break;
                        }

                        if ((bp = allocb(STRUCT_SIZE(sb), BPRI_MED)) == NULL) {

                                tilog(
                        "ti_doname: allocb failed no recovery attempt\n", 0);

                                freemsg(csp->cp_private);
                                miocnak(q, mp, 0, EAGAIN);
                                ret = DONAME_FAIL;
                                break;
                        }
                        bp->b_wptr += STRUCT_SIZE(sb);
                        bcopy(STRUCT_BUF(sb), bp->b_rptr, STRUCT_SIZE(sb));
                        cqp->cq_addr =
                            (caddr_t)*(intptr_t *)csp->cp_private->b_rptr;
                        cqp->cq_size = STRUCT_SIZE(sb);
                        cqp->cq_flag = 0;
                        mp->b_datap->db_type = M_COPYOUT;
                        mp->b_cont = bp;
                        STRUCT_FSET(sb, len, 0);
                        STRUCT_FSET(sb, maxlen, 0); /* ack next time around */
                        qreply(q, mp);
                        ret = DONAME_CONT;
                        break;
                }

                /*
                 * copy the address to the user
                 */
                if ((bp = allocb((size_t)STRUCT_FGET(sb, len), BPRI_MED))
                    == NULL) {
                        if (getpeer)
                                mutex_exit(&tp->tim_mutex);

                        tilog("ti_doname: allocb failed no recovery attempt\n",
                            0);

                        freemsg(csp->cp_private);
                        miocnak(q, mp, 0, EAGAIN);
                        ret = DONAME_FAIL;
                        break;
                }
                bp->b_wptr += STRUCT_FGET(sb, len);
                if (getpeer) {
                        bcopy(tp->tim_peername, bp->b_rptr,
                            STRUCT_FGET(sb, len));
                        mutex_exit(&tp->tim_mutex);
                } else {
                        bcopy(tp->tim_myname, bp->b_rptr, STRUCT_FGET(sb, len));
                }
                cqp->cq_addr = (caddr_t)STRUCT_FGETP(sb, buf);
                cqp->cq_size = STRUCT_FGET(sb, len);
                cqp->cq_flag = 0;
                mp->b_datap->db_type = M_COPYOUT;
                mp->b_cont = bp;
                STRUCT_FSET(sb, len, 0); /* copy the strbuf next time around */
                qreply(q, mp);
                ret = DONAME_CONT;
                break;

        default:
                tilog("ti_doname: freeing bad message type = %d\n",
                    mp->b_datap->db_type);
                freemsg(mp);
                ret = DONAME_FAIL;
                break;
        }
        return (ret);
}


/*
 * Fill in the address of a connectionless data packet if a connect
 * had been done on this endpoint.
 */
static mblk_t *
tim_filladdr(queue_t *q, mblk_t *mp, boolean_t dorecover)
{
        mblk_t *bp;
        struct tim_tim *tp;
        struct T_unitdata_req *up;
        struct T_unitdata_req *nup;
        size_t plen;

        tp = (struct tim_tim *)q->q_ptr;
        if (mp->b_datap->db_type == M_DATA) {
                mutex_enter(&tp->tim_mutex);
                bp = allocb(sizeof (struct T_unitdata_req) + tp->tim_peerlen,
                    BPRI_MED);
                if (bp != NULL) {
                        bp->b_datap->db_type = M_PROTO;
                        up = (struct T_unitdata_req *)bp->b_rptr;
                        up->PRIM_type = T_UNITDATA_REQ;
                        up->DEST_length = tp->tim_peerlen;
                        bp->b_wptr += sizeof (struct T_unitdata_req);
                        up->DEST_offset = sizeof (struct T_unitdata_req);
                        up->OPT_length = 0;
                        up->OPT_offset = 0;
                        if (tp->tim_peerlen > 0) {
                                bcopy(tp->tim_peername, bp->b_wptr,
                                    tp->tim_peerlen);
                                bp->b_wptr += tp->tim_peerlen;
                        }
                        bp->b_cont = mp;
                }
        } else {
                ASSERT(mp->b_datap->db_type == M_PROTO);
                up = (struct T_unitdata_req *)mp->b_rptr;
                ASSERT(up->PRIM_type == T_UNITDATA_REQ);
                if (up->DEST_length != 0)
                        return (mp);
                mutex_enter(&tp->tim_mutex);
                bp = allocb(sizeof (struct T_unitdata_req) + up->OPT_length +
                    tp->tim_peerlen, BPRI_MED);
                if (bp != NULL) {
                        bp->b_datap->db_type = M_PROTO;
                        nup = (struct T_unitdata_req *)bp->b_rptr;
                        nup->PRIM_type = T_UNITDATA_REQ;
                        nup->DEST_length = plen = tp->tim_peerlen;
                        bp->b_wptr += sizeof (struct T_unitdata_req);
                        nup->DEST_offset = sizeof (struct T_unitdata_req);
                        if (plen > 0) {
                                bcopy(tp->tim_peername, bp->b_wptr, plen);
                                bp->b_wptr += plen;
                        }
                        mutex_exit(&tp->tim_mutex);
                        if (up->OPT_length == 0) {
                                nup->OPT_length = 0;
                                nup->OPT_offset = 0;
                        } else {
                                nup->OPT_length = up->OPT_length;
                                nup->OPT_offset =
                                    sizeof (struct T_unitdata_req) + plen;
                                bcopy((mp->b_wptr + up->OPT_offset), bp->b_wptr,
                                    up->OPT_length);
                                bp->b_wptr += up->OPT_length;
                        }
                        bp->b_cont = mp->b_cont;
                        mp->b_cont = NULL;
                        freeb(mp);
                        return (bp);
                }
        }
        ASSERT(MUTEX_HELD(&tp->tim_mutex));
        if (bp == NULL && dorecover) {
                tim_recover(q, mp,
                    sizeof (struct T_unitdata_req) + tp->tim_peerlen);
        }
        mutex_exit(&tp->tim_mutex);
        return (bp);
}

static void
tim_addlink(struct tim_tim *tp)
{
        struct tim_tim **tpp;
        struct tim_tim  *next;

        tpp = &tim_hash[TIM_HASH(tp->tim_acceptor)];
        rw_enter(&tim_list_rwlock, RW_WRITER);

        if ((next = *tpp) != NULL)
                next->tim_ptpn = &tp->tim_next;
        tp->tim_next = next;
        tp->tim_ptpn = tpp;
        *tpp = tp;

        tim_cnt++;

        rw_exit(&tim_list_rwlock);
}

static void
tim_dellink(struct tim_tim *tp)
{
        struct tim_tim  *next;

        rw_enter(&tim_list_rwlock, RW_WRITER);

        if ((next = tp->tim_next) != NULL)
                next->tim_ptpn = tp->tim_ptpn;
        *(tp->tim_ptpn) = next;

        tim_cnt--;

        rw_exit(&tim_list_rwlock);
}

static struct tim_tim *
tim_findlink(t_uscalar_t id)
{
        struct tim_tim  *tp;

        ASSERT(rw_lock_held(&tim_list_rwlock));

        for (tp = tim_hash[TIM_HASH(id)]; tp != NULL; tp = tp->tim_next) {
                if (tp->tim_acceptor == id) {
                        break;
                }
        }
        return (tp);
}

static void
tim_recover(queue_t *q, mblk_t *mp, t_scalar_t size)
{
        struct tim_tim  *tp;
        bufcall_id_t    bid;
        timeout_id_t    tid;

        tp = (struct tim_tim *)q->q_ptr;

        /*
         * Avoid re-enabling the queue.
         */
        if (mp->b_datap->db_type == M_PCPROTO)
                mp->b_datap->db_type = M_PROTO;
        noenable(q);
        (void) putbq(q, mp);

        /*
         * Make sure there is at most one outstanding request per queue.
         */
        if (q->q_flag & QREADR) {
                if (tp->tim_rtimoutid || tp->tim_rbufcid)
                        return;
        } else {
                if (tp->tim_wtimoutid || tp->tim_wbufcid)
                        return;
        }
        if (!(bid = qbufcall(RD(q), (size_t)size, BPRI_MED, tim_buffer, q))) {
                tid = qtimeout(RD(q), tim_timer, q, TIMWAIT);
                if (q->q_flag & QREADR)
                        tp->tim_rtimoutid = tid;
                else
                        tp->tim_wtimoutid = tid;
        } else  {
                if (q->q_flag & QREADR)
                        tp->tim_rbufcid = bid;
                else
                        tp->tim_wbufcid = bid;
        }
}

/*
 * Timod is waiting on a downstream ioctl reply, come back soon
 * to reschedule the write side service routine, which will check
 * if the ioctl is done and another can proceed.
 */
static void
tim_ioctl_retry(queue_t *q)
{
        struct tim_tim  *tp;

        tp = (struct tim_tim *)q->q_ptr;

        /*
         * Make sure there is at most one outstanding request per wqueue.
         */
        if (tp->tim_wtimoutid || tp->tim_wbufcid)
                return;

        tp->tim_wtimoutid = qtimeout(RD(q), tim_timer, q, TIMIOCWAIT);
}

/*
 * Inspect the data on read queues starting from read queues passed as
 * paramter (timod read queue) and traverse until
 * q_next is NULL (stream head). Look for a TPI T_EXDATA_IND message
 * reutrn 1 if found, 0 if not found.
 */
static int
ti_expind_on_rdqueues(queue_t *rq)
{
        mblk_t *bp;
        queue_t *q;

        q = rq;
        /*
         * We are going to walk q_next, so protect stream from plumbing
         * changes.
         */
        claimstr(q);
        do {
                /*
                 * Hold QLOCK while referencing data on queues
                 */
                mutex_enter(QLOCK(rq));
                bp = rq->q_first;
                while (bp != NULL) {
                        /*
                         * Walk the messages on the queue looking
                         * for a possible T_EXDATA_IND
                         */
                        if ((bp->b_datap->db_type == M_PROTO) &&
                            ((bp->b_wptr - bp->b_rptr) >=
                            sizeof (struct T_exdata_ind)) &&
                            (((struct T_exdata_ind *)bp->b_rptr)->PRIM_type
                            == T_EXDATA_IND)) {
                                /* bp is T_EXDATA_IND */
                                mutex_exit(QLOCK(rq));
                                releasestr(q); /* decrement sd_refcnt  */
                                return (1); /* expdata is on a read queue */
                        }
                        bp = bp->b_next; /* next message */
                }
                mutex_exit(QLOCK(rq));
                rq = rq->q_next;        /* next upstream queue */
        } while (rq != NULL);
        releasestr(q);
        return (0);             /* no expdata on read queues */
}

static void
tim_tcap_timer(void *q_ptr)
{
        queue_t *q = (queue_t *)q_ptr;
        struct tim_tim *tp = (struct tim_tim *)q->q_ptr;

        ASSERT(tp != NULL && tp->tim_tcap_timoutid != 0);
        ASSERT((tp->tim_flags & TI_CAP_RECVD) != 0);

        tp->tim_tcap_timoutid = 0;
        TILOG("tim_tcap_timer: fired\n", 0);
        tim_tcap_genreply(q, tp);
}

/*
 * tim_tcap_genreply() is called either from timeout routine or when
 * T_ERROR_ACK is received. In both cases it means that underlying
 * transport doesn't provide T_CAPABILITY_REQ.
 */
static void
tim_tcap_genreply(queue_t *q, struct tim_tim *tp)
{
        mblk_t          *mp = tp->tim_iocsave;
        struct iocblk   *iocbp;

        TILOG("timodrproc: tim_tcap_genreply\n", 0);

        ASSERT(tp == (struct tim_tim *)q->q_ptr);
        ASSERT(mp != NULL);

        iocbp = (struct iocblk *)mp->b_rptr;
        ASSERT(iocbp != NULL);
        ASSERT(MBLKL(mp) == sizeof (struct iocblk));
        ASSERT(iocbp->ioc_cmd == TI_CAPABILITY);
        ASSERT(mp->b_cont == NULL);

        /* Save this information permanently in the module */
        PI_PROVLOCK(tp->tim_provinfo);
        if (tp->tim_provinfo->tpi_capability == PI_DONTKNOW)
                tp->tim_provinfo->tpi_capability = PI_NO;
        PI_PROVUNLOCK(tp->tim_provinfo);

        if (tp->tim_tcap_timoutid != 0) {
                (void) quntimeout(q, tp->tim_tcap_timoutid);
                tp->tim_tcap_timoutid = 0;
        }

        if ((tp->tim_flags & CAP_WANTS_INFO) != 0) {
                /* Send T_INFO_REQ down */
                mblk_t *tirmp = tpi_ack_alloc(NULL,
                    sizeof (struct T_info_req), M_PCPROTO, T_INFO_REQ);

                if (tirmp != NULL) {
                        /* Emulate TC1_INFO */
                        TILOG("emulate_tcap_ioc_req: sending T_INFO_REQ\n", 0);
                        tp->tim_flags |= WAIT_IOCINFOACK;
                        putnext(WR(q), tirmp);
                } else {
                        tilog("emulate_tcap_req: allocb fail, "
                            "no recovery attmpt\n", 0);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(TI_CAP_RECVD | WAITIOCACK |
                            CAP_WANTS_INFO | WAIT_IOCINFOACK);
                        miocnak(q, mp, 0, ENOMEM);
                }
        } else {
                /* Reply immediately */
                mblk_t *ackmp = tpi_ack_alloc(NULL,
                    sizeof (struct T_capability_ack), M_PCPROTO,
                    T_CAPABILITY_ACK);

                mp->b_cont = ackmp;

                if (ackmp != NULL) {
                        ((struct T_capability_ack *)
                            ackmp->b_rptr)->CAP_bits1 = 0;
                        tim_ioctl_send_reply(q, mp, ackmp);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(WAITIOCACK | WAIT_IOCINFOACK |
                            TI_CAP_RECVD | CAP_WANTS_INFO);
                } else {
                        tilog("timodwproc:allocb failed no "
                            "recovery attempt\n", 0);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(TI_CAP_RECVD | WAITIOCACK |
                            CAP_WANTS_INFO | WAIT_IOCINFOACK);
                        miocnak(q, mp, 0, ENOMEM);
                }
        }
}


static void
tim_ioctl_send_reply(queue_t *q, mblk_t *ioc_mp, mblk_t *mp)
{
        struct iocblk   *iocbp;

        ASSERT(q != NULL && ioc_mp != NULL);

        ioc_mp->b_datap->db_type = M_IOCACK;
        if (mp != NULL)
                mp->b_datap->db_type = M_DATA;

        if (ioc_mp->b_cont != mp) {
                /* It is safe to call freemsg for NULL pointers */
                freemsg(ioc_mp->b_cont);
                ioc_mp->b_cont = mp;
        }
        iocbp = (struct iocblk *)ioc_mp->b_rptr;
        iocbp->ioc_error = 0;
        iocbp->ioc_rval = 0;
        /*
         * All ioctl's may return more data than was specified by
         * count arg. For TI_CAPABILITY count is treated as maximum data size.
         */
        if (mp == NULL)
                iocbp->ioc_count = 0;
        else if (iocbp->ioc_cmd != TI_CAPABILITY)
                iocbp->ioc_count = msgsize(mp);
        else {
                iocbp->ioc_count = MIN(MBLKL(mp), iocbp->ioc_count);
                /* Truncate message if too large */
                mp->b_wptr = mp->b_rptr + iocbp->ioc_count;
        }

        TILOG("iosendreply: ioc_cmd = %d, ", iocbp->ioc_cmd);
        putnext(RD(q), ioc_mp);
}

/*
 * Send M_IOCACK for errors.
 */
static void
tim_send_ioc_error_ack(queue_t *q, struct tim_tim *tp, mblk_t *mp)
{
        struct T_error_ack *tea = (struct T_error_ack *)mp->b_rptr;
        t_scalar_t error_prim;

        mp->b_wptr = mp->b_rptr + sizeof (struct T_error_ack);
        ASSERT(mp->b_wptr <= mp->b_datap->db_lim);
        error_prim = tea->ERROR_prim;

        ASSERT(tp->tim_iocsave != NULL);
        ASSERT(tp->tim_iocsave->b_cont != mp);

        /* Always send this to the read side of the queue */
        q = RD(q);

        TILOG("tim_send_ioc_error_ack: prim = %d\n", tp->tim_saved_prim);

        if (tp->tim_saved_prim != error_prim) {
                putnext(q, mp);
        } else if (error_prim == T_CAPABILITY_REQ) {
                TILOG("timodrproc: T_ERROR_ACK/T_CAPABILITY_REQ\n", 0);
                ASSERT(tp->tim_iocsave->b_cont == NULL);

                tim_tcap_genreply(q, tp);
                freemsg(mp);
        } else {
                struct iocblk *iocbp = (struct iocblk *)tp->tim_iocsave->b_rptr;

                TILOG("tim_send_ioc_error_ack: T_ERROR_ACK: prim %d\n",
                    error_prim);
                ASSERT(tp->tim_iocsave->b_cont == NULL);

                switch (error_prim) {
                default:
                        TILOG("timodrproc: Unknown T_ERROR_ACK:  tlierror %d\n",
                            tea->TLI_error);

                        putnext(q, mp);
                        break;

                case T_INFO_REQ:
                case T_SVR4_OPTMGMT_REQ:
                case T_OPTMGMT_REQ:
                case O_T_BIND_REQ:
                case T_BIND_REQ:
                case T_UNBIND_REQ:
                case T_ADDR_REQ:
                case T_CAPABILITY_REQ:

                        TILOG("ioc_err_ack: T_ERROR_ACK: tlierror %x\n",
                            tea->TLI_error);

                        /* get saved ioctl msg and set values */
                        iocbp->ioc_count = 0;
                        iocbp->ioc_error = 0;
                        iocbp->ioc_rval = tea->TLI_error;
                        if (iocbp->ioc_rval == TSYSERR)
                                iocbp->ioc_rval |= tea->UNIX_error << 8;
                        tp->tim_iocsave->b_datap->db_type = M_IOCACK;
                        freemsg(mp);
                        putnext(q, tp->tim_iocsave);
                        tp->tim_iocsave = NULL;
                        tp->tim_saved_prim = -1;
                        tp->tim_flags &= ~(WAITIOCACK | TI_CAP_RECVD |
                            CAP_WANTS_INFO | WAIT_IOCINFOACK);
                        break;
                }
        }
}

/*
 * Send reply to a usual message or ioctl message upstream.
 * Should be called from the read side only.
 */
static void
tim_send_reply(queue_t *q, mblk_t *mp, struct tim_tim *tp, t_scalar_t prim)
{
        ASSERT(mp != NULL && q != NULL && tp != NULL);
        ASSERT(q == RD(q));

        /* Restore db_type - recover() might have changed it */
        mp->b_datap->db_type = M_PCPROTO;

        if (((tp->tim_flags & WAITIOCACK) == 0) || (tp->tim_saved_prim != prim))
                putnext(q, mp);
        else {
                ASSERT(tp->tim_iocsave != NULL);
                tim_ioctl_send_reply(q, tp->tim_iocsave, mp);
                tp->tim_iocsave = NULL;
                tp->tim_saved_prim = -1;
                tp->tim_flags &= ~(WAITIOCACK | WAIT_IOCINFOACK |
                    TI_CAP_RECVD | CAP_WANTS_INFO);
        }
}

/*
 * Reply to TI_SYNC reequest without sending anything downstream.
 */
static void
tim_answer_ti_sync(queue_t *q, mblk_t *mp, struct tim_tim *tp,
    mblk_t *ackmp, uint32_t tsr_flags)
{
        struct ti_sync_ack *tsap;

        ASSERT(q != NULL && q == WR(q) && ackmp != NULL);

        tsap = (struct ti_sync_ack *)ackmp->b_rptr;
        bzero(tsap, sizeof (struct ti_sync_ack));
        ackmp->b_wptr = ackmp->b_rptr + sizeof (struct ti_sync_ack);

        if (tsr_flags == 0 ||
            (tsr_flags & ~(TSRF_QLEN_REQ | TSRF_IS_EXP_IN_RCVBUF)) != 0) {
                /*
                 * unsupported/bad flag setting
                 * or no flag set.
                 */
                TILOG("timodwproc: unsupported/bad flag setting %x\n",
                    tsr_flags);
                freemsg(ackmp);
                miocnak(q, mp, 0, EINVAL);
                return;
        }

        if ((tsr_flags & TSRF_QLEN_REQ) != 0)
                tsap->tsa_qlen = tp->tim_backlog;

        if ((tsr_flags & TSRF_IS_EXP_IN_RCVBUF) != 0 &&
            ti_expind_on_rdqueues(RD(q))) {
                /*
                 * Expedited data is queued on
                 * the stream read side
                 */
                tsap->tsa_flags |= TSAF_EXP_QUEUED;
        }

        tim_ioctl_send_reply(q, mp, ackmp);
        tp->tim_iocsave = NULL;
        tp->tim_saved_prim = -1;
        tp->tim_flags &= ~(WAITIOCACK | WAIT_IOCINFOACK |
            TI_CAP_RECVD | CAP_WANTS_INFO);
}

/*
 * Send TPI message from IOCTL message, ssave original ioctl header and TPI
 * message type. Should be called from write side only.
 */
static void
tim_send_ioctl_tpi_msg(queue_t *q, mblk_t *mp, struct tim_tim *tp,
    struct iocblk *iocb)
{
        mblk_t *tmp;
        int ioc_cmd = iocb->ioc_cmd;

        ASSERT(q != NULL && mp != NULL && tp != NULL);
        ASSERT(q == WR(q));
        ASSERT(mp->b_cont != NULL);

        tp->tim_iocsave = mp;
        tmp = mp->b_cont;

        mp->b_cont = NULL;
        tp->tim_flags |= WAITIOCACK;
        tp->tim_saved_prim = ((union T_primitives *)tmp->b_rptr)->type;

        /*
         * For TI_GETINFO, the attached message is a T_INFO_REQ
         * For TI_SYNC, we generate the T_INFO_REQ message above
         * For TI_CAPABILITY the attached message is either
         * T_CAPABILITY_REQ or T_INFO_REQ.
         * Among TPI request messages possible,
         *      T_INFO_REQ/T_CAPABILITY_ACK messages are a M_PCPROTO, rest
         *      are M_PROTO
         */
        if (ioc_cmd == TI_GETINFO || ioc_cmd == TI_SYNC ||
            ioc_cmd == TI_CAPABILITY) {
                tmp->b_datap->db_type = M_PCPROTO;
        } else {
                tmp->b_datap->db_type = M_PROTO;
        }

        /* Verify credentials in STREAM */
        ASSERT(iocb->ioc_cr == NULL || iocb->ioc_cr == DB_CRED(tmp));

        ASSERT(DB_CRED(tmp) != NULL);

        TILOG("timodwproc: sending down %d\n", tp->tim_saved_prim);
        putnext(q, tmp);
}

static void
tim_clear_peer(struct tim_tim *tp)
{
        mutex_enter(&tp->tim_mutex);
        if (tp->tim_peercred != NULL) {
                crfree(tp->tim_peercred);
                tp->tim_peercred = NULL;
        }
        tp->tim_peerlen = 0;
        mutex_exit(&tp->tim_mutex);
}