root/usr/src/uts/common/inet/mi.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/* Copyright (c) 1990 Mentat Inc. */

#include <sys/types.h>
#include <inet/common.h>        /* for various inet/mi.h and inet/nd.h needs */
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/sysmacros.h>
#include <inet/nd.h>
#include <inet/mi.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/timod.h>
#include <sys/vtrace.h>
#include <sys/kmem.h>
#include <sys/mkdev.h>
#include <sys/strlog.h>
#include <sys/ddi.h>
#include <sys/suntpi.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/kobj.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <inet/proto_set.h>

#define ISDIGIT(ch)     ((ch) >= '0' && (ch) <= '9')
#define ISUPPER(ch)     ((ch) >= 'A' && (ch) <= 'Z')
#define tolower(ch)     ('a' + ((ch) - 'A'))

#define MI_IS_TRANSPARENT(mp)   (mp->b_cont && \
        (mp->b_cont->b_rptr != mp->b_cont->b_wptr))

/*
 * NOTE: Whenever anything is allocated by mi_alloc or mi_alloc_sleep (below),
 * the size of the requested allocation is increased by one word.  This extra
 * word is used to store the size of the object being allocated, and is located
 * at the beginning of the allocated block.  The pointer returned to the caller
 * is a pointer to the *second* word in the newly-allocated block.  The IP
 * module of mdb is aware of this, and will need to be changed if this
 * allocation strategy is changed.
 */

typedef struct  stroptions *STROPTP;
typedef union T_primitives *TPRIMP;

/* Timer block states. */
#define TB_RUNNING      1
#define TB_IDLE         2
/*
 * Could not stop/free before putq
 */
#define TB_RESCHED      3       /* mtb_time_left contains tick count */
#define TB_CANCELLED    4
#define TB_TO_BE_FREED  5

typedef struct mtb_s {
        int             mtb_state;
        timeout_id_t    mtb_tid;
        queue_t         *mtb_q;
        MBLKP           mtb_mp;
        clock_t         mtb_time_left;
} MTB, *MTBP;

static void mi_timer_fire(void *);
static int mi_iprintf(char *, va_list, pfi_t, char *);
static void mi_tpi_addr_and_opt(MBLKP, char *, t_scalar_t, char *, t_scalar_t);
static MBLKP mi_tpi_trailer_alloc(MBLKP, size_t, t_scalar_t);

/* ARGSUSED1 */
void *
mi_alloc(size_t size, uint_t pri)
{
        size_t *ptr;

        size += sizeof (size);
        if (ptr = kmem_alloc(size, KM_NOSLEEP)) {
                *ptr = size;
                return (ptr + 1);
        }
        return (NULL);
}

/* ARGSUSED1 */
void *
mi_alloc_sleep(size_t size, uint_t pri)
{
        size_t *ptr;

        size += sizeof (size);
        ptr = kmem_alloc(size, KM_SLEEP);
        *ptr = size;
        return (ptr + 1);
}

int
mi_close_comm(void **mi_headp, queue_t *q)
{
        IDP ptr;

        ptr = q->q_ptr;
        mi_close_unlink(mi_headp, ptr);
        mi_close_free(ptr);
        q->q_ptr = WR(q)->q_ptr = NULL;
        return (0);
}

void
mi_close_unlink(void **mi_headp, IDP ptr)
{
        mi_head_t       *mi_head = *(mi_head_t **)mi_headp;
        MI_OP           mi_o;
        dev_t           dev;

        mi_o = (MI_OP)ptr;
        if (!mi_o)
                return;
        mi_o--;

        if (mi_o->mi_o_next == NULL) {
                /* Not in list */
                ASSERT(mi_o->mi_o_prev == NULL);
                return;
        }

        /* Free minor number */
        dev = mi_o->mi_o_dev;
        if ((dev != OPENFAIL) && (dev != 0) && (dev <= MAXMIN))
                inet_minor_free(mi_head->mh_arena, dev);

        /* Unlink from list */
        ASSERT(mi_o->mi_o_next != NULL);
        ASSERT(mi_o->mi_o_prev != NULL);
        ASSERT(mi_o->mi_o_next->mi_o_prev == mi_o);
        ASSERT(mi_o->mi_o_prev->mi_o_next == mi_o);

        mi_o->mi_o_next->mi_o_prev = mi_o->mi_o_prev;
        mi_o->mi_o_prev->mi_o_next = mi_o->mi_o_next;
        mi_o->mi_o_next = mi_o->mi_o_prev = NULL;

        mi_o->mi_o_dev = (dev_t)OPENFAIL;

        /* If list now empty free the list head */
        if (mi_head->mh_o.mi_o_next == &mi_head->mh_o) {
                ASSERT(mi_head->mh_o.mi_o_prev == &mi_head->mh_o);
                if (mi_head->mh_arena != NULL)
                        inet_minor_destroy(mi_head->mh_arena);
                mi_free((IDP)mi_head);
                *mi_headp = NULL;
        }
}

void
mi_close_free(IDP ptr)
{
        MI_OP           mi_o;

        mi_o = (MI_OP)ptr;
        if (!mi_o)
                return;
        mi_o--;

        ASSERT(mi_o->mi_o_next == NULL && mi_o->mi_o_prev == NULL);
        mi_free((IDP)mi_o);
}

/*
 * mi_copyin - takes care of transparent or non-transparent ioctl for the
 * calling function so that they have to deal with just M_IOCDATA type
 * and not worry about M_COPYIN.
 *
 * mi_copyin checks to see if the ioctl is transparent or non transparent.
 * In case of a non_transparent ioctl, it packs the data into a M_IOCDATA
 * message and puts it back onto the current queue for further processing.
 * In case of transparent ioctl, it sends a M_COPYIN message up to the
 * streamhead so that a M_IOCDATA with the information comes back down.
 */
void
mi_copyin(queue_t *q, MBLKP mp, char *uaddr, size_t len)
{
        struct  iocblk *iocp = (struct iocblk *)mp->b_rptr;
        struct  copyreq *cq = (struct copyreq *)mp->b_rptr;
        struct  copyresp *cp = (struct copyresp *)mp->b_rptr;
        int     err;
        MBLKP   mp1;

        ASSERT(mp->b_datap->db_type == M_IOCTL && !uaddr);

        /* A transparent ioctl. Send a M_COPYIN message to the streamhead. */
        if (iocp->ioc_count == TRANSPARENT) {
                MI_COPY_COUNT(mp) = 1;
                MI_COPY_DIRECTION(mp) = MI_COPY_IN;
                cq->cq_private = mp->b_cont;
                cq->cq_size = len;
                cq->cq_flag = 0;
                bcopy(mp->b_cont->b_rptr, &cq->cq_addr, sizeof (cq->cq_addr));
                mp->b_cont = NULL;
                mp->b_datap->db_type = M_COPYIN;
                qreply(q, mp);
                return;
        }

        /*
         * A non-transparent ioctl. Need to convert into M_IOCDATA message.
         *
         * We allocate a 0 byte message block and put its address in
         * cp_private. It also makes the b_prev field = 1 and b_next
         * field = MI_COPY_IN for this 0 byte block. This is done to
         * maintain compatibility with old code in mi_copy_state
         * (which removes the empty block).
         */
        err = miocpullup(mp, len);
        if (err != 0)
                goto err_ret;

        mp1 = allocb(0, BPRI_MED);
        if (mp1 == NULL) {
                err = ENOMEM;
                goto err_ret;
        }

        /*
         * Temporarily insert mp1 between the M_IOCTL and M_DATA blocks so
         * that we can use the MI_COPY_COUNT & MI_COPY_DIRECTION macros.
         */
        mp1->b_cont = mp->b_cont;
        mp->b_cont = mp1;
        MI_COPY_COUNT(mp) = 1;
        MI_COPY_DIRECTION(mp) = MI_COPY_IN;
        mp->b_cont = mp1->b_cont;
        mp1->b_cont = NULL;

        /*
         * Leave a pointer to the 0 byte block in cp_private field for
         * future use by the mi_copy_* routines.
         */
        mp->b_datap->db_type = M_IOCDATA;
        cp->cp_private = mp1;
        cp->cp_rval = NULL;
        put(q, mp);
        return;

err_ret:
        iocp->ioc_error = err;
        iocp->ioc_count = 0;
        if (mp->b_cont) {
                freemsg(mp->b_cont);
                mp->b_cont = NULL;
        }
        mp->b_datap->db_type = M_IOCACK;
        qreply(q, mp);
}

/*
 * Allows transparent IOCTLs to have multiple copyins.  This is needed
 * for some variable-length structures, where the total size is only known
 * after the first part is copied in. Rather than setting MI_COPY_COUNT to
 * 1, as in mi_coypin(), it is simply incremented here.  This value can
 * then be checked in the returned IOCBLK.
 *
 * As this deals with copyins that follow the initial copyin, the byte
 * offset into the user buffer from which copying should begin must be
 * passed in in the offset parameter.
 *
 * Unlike mi_coypin(), this function expects to be passed an mblk chain
 * headed by an M_IOCBLK, as that's the chain that will be in use for
 * copies after the first one (copies where n != 1).
 */
void
mi_copyin_n(queue_t *q, MBLKP mp, size_t offset, size_t len)
{
        struct  copyreq *cq = (struct copyreq *)mp->b_rptr;

        ASSERT(mp->b_datap->db_type == M_IOCDATA);

        MI_COPY_COUNT(mp)++;
        MI_COPY_DIRECTION(mp) = MI_COPY_IN;
        cq->cq_private = mp->b_cont;
        cq->cq_size = len;
        cq->cq_flag = 0;
        bcopy(mp->b_cont->b_rptr, &cq->cq_addr, sizeof (cq->cq_addr));
        cq->cq_addr += offset;
        mp->b_cont = NULL;
        mp->b_datap->db_type = M_COPYIN;
        qreply(q, mp);
}

void
mi_copyout(queue_t *q, MBLKP mp)
{
        struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
        struct copyreq *cq = (struct copyreq *)iocp;
        struct copyresp *cp = (struct copyresp *)cq;
        MBLKP   mp1;
        MBLKP   mp2;

        if (mp->b_datap->db_type != M_IOCDATA || !mp->b_cont) {
                mi_copy_done(q, mp, EPROTO);
                return;
        }
        /* Check completion of previous copyout operation. */
        mp1 = mp->b_cont;
        if ((int)(uintptr_t)cp->cp_rval || !mp1->b_cont) {
                mi_copy_done(q, mp, (int)(uintptr_t)cp->cp_rval);
                return;
        }
        if (!mp1->b_cont->b_cont && !MI_IS_TRANSPARENT(mp)) {
                mp1->b_next = NULL;
                mp1->b_prev = NULL;
                mp->b_cont = mp1->b_cont;
                freeb(mp1);
                mp1 = mp->b_cont;
                mp1->b_next = NULL;
                mp1->b_prev = NULL;
                iocp->ioc_count = mp1->b_wptr - mp1->b_rptr;
                iocp->ioc_error = 0;
                mp->b_datap->db_type = M_IOCACK;
                qreply(q, mp);
                return;
        }
        if (MI_COPY_DIRECTION(mp) == MI_COPY_IN) {
                /* Set up for first copyout. */
                MI_COPY_DIRECTION(mp) = MI_COPY_OUT;
                MI_COPY_COUNT(mp) = 1;
        } else {
                ++MI_COPY_COUNT(mp);
        }
        cq->cq_private = mp1;
        /* Find message preceding last. */
        for (mp2 = mp1; mp2->b_cont->b_cont; mp2 = mp2->b_cont)
                ;
        if (mp2 == mp1)
                bcopy((char *)mp1->b_rptr, (char *)&cq->cq_addr,
                    sizeof (cq->cq_addr));
        else
                cq->cq_addr = (char *)mp2->b_cont->b_next;
        mp1 = mp2->b_cont;
        mp->b_datap->db_type = M_COPYOUT;
        mp->b_cont = mp1;
        mp2->b_cont = NULL;
        mp1->b_next = NULL;
        cq->cq_size = mp1->b_wptr - mp1->b_rptr;
        cq->cq_flag = 0;
        qreply(q, mp);
}

MBLKP
mi_copyout_alloc(queue_t *q, MBLKP mp, char *uaddr, size_t len,
    boolean_t free_on_error)
{
        struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
        MBLKP   mp1;

        if (mp->b_datap->db_type == M_IOCTL) {
                if (iocp->ioc_count != TRANSPARENT) {
                        mp1 = allocb(0, BPRI_MED);
                        if (mp1 == NULL) {
                                if (free_on_error) {
                                        iocp->ioc_error = ENOMEM;
                                        iocp->ioc_count = 0;
                                        freemsg(mp->b_cont);
                                        mp->b_cont = NULL;
                                        mp->b_datap->db_type = M_IOCACK;
                                        qreply(q, mp);
                                }
                                return (NULL);
                        }
                        mp1->b_cont = mp->b_cont;
                        mp->b_cont = mp1;
                }
                MI_COPY_COUNT(mp) = 0;
                MI_COPY_DIRECTION(mp) = MI_COPY_OUT;
                /* Make sure it looks clean to mi_copyout. */
                mp->b_datap->db_type = M_IOCDATA;
                ((struct copyresp *)iocp)->cp_rval = NULL;
        }
        mp1 = allocb(len, BPRI_MED);
        if (mp1 == NULL) {
                if (free_on_error)
                        mi_copy_done(q, mp, ENOMEM);
                return (NULL);
        }
        linkb(mp, mp1);
        mp1->b_next = (MBLKP)uaddr;
        return (mp1);
}

void
mi_copy_done(queue_t *q, MBLKP mp, int err)
{
        struct iocblk *iocp;
        MBLKP   mp1;

        if (!mp)
                return;
        if (!q || (mp->b_wptr - mp->b_rptr) < sizeof (struct iocblk)) {
                freemsg(mp);
                return;
        }
        iocp = (struct iocblk *)mp->b_rptr;
        mp->b_datap->db_type = M_IOCACK;
        iocp->ioc_error = err;

        iocp->ioc_count = 0;
        if ((mp1 = mp->b_cont) != NULL) {
                for (; mp1; mp1 = mp1->b_cont) {
                        mp1->b_prev = NULL;
                        mp1->b_next = NULL;
                }
                freemsg(mp->b_cont);
                mp->b_cont = NULL;
        }
        qreply(q, mp);
}

int
mi_copy_state(queue_t *q, MBLKP mp, MBLKP *mpp)
{
        struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
        struct copyresp *cp = (struct copyresp *)iocp;
        MBLKP   mp1;

        mp1 = mp->b_cont;
        mp->b_cont = cp->cp_private;
        if (mp1) {
                if (mp1->b_cont && !pullupmsg(mp1, -1)) {
                        mi_copy_done(q, mp, ENOMEM);
                        return (-1);
                }
                linkb(mp->b_cont, mp1);
        }
        if ((int)(uintptr_t)cp->cp_rval) {
                mi_copy_done(q, mp, (int)(uintptr_t)cp->cp_rval);
                return (-1);
        }
        if (mpp && MI_COPY_DIRECTION(mp) == MI_COPY_IN)
                *mpp = mp1;
        return (MI_COPY_STATE(mp));
}

void
mi_free(void *ptr)
{
        size_t  size;

        if (!ptr)
                return;
        if ((size = ((size_t *)ptr)[-1]) <= 0)
                cmn_err(CE_PANIC, "mi_free");

        kmem_free((void *) ((size_t *)ptr - 1), size);
}

static int
mi_iprintf(char *fmt, va_list ap, pfi_t putc_func, char *cookie)
{
        int     base;
        char    buf[(sizeof (long) * 3) + 1];
        static char     hex_val[] = "0123456789abcdef";
        int     ch;
        int     count;
        char    *cp1;
        int     digits;
        char    *fcp;
        boolean_t       is_long;
        ulong_t uval;
        long    val;
        boolean_t       zero_filled;

        if (!fmt)
                return (-1);
        count = 0;
        while (*fmt) {
                if (*fmt != '%' || *++fmt == '%') {
                        count += (*putc_func)(cookie, *fmt++);
                        continue;
                }
                if (*fmt == '0') {
                        zero_filled = B_TRUE;
                        fmt++;
                        if (!*fmt)
                                break;
                } else
                        zero_filled = B_FALSE;
                base = 0;
                for (digits = 0; ISDIGIT(*fmt); fmt++) {
                        digits *= 10;
                        digits += (*fmt - '0');
                }
                if (!*fmt)
                        break;
                is_long = B_FALSE;
                if (*fmt == 'l') {
                        is_long = B_TRUE;
                        fmt++;
                }
                if (!*fmt)
                        break;
                ch = *fmt++;
                if (ISUPPER(ch)) {
                        ch = tolower(ch);
                        is_long = B_TRUE;
                }
                switch (ch) {
                case 'c':
                        count += (*putc_func)(cookie, va_arg(ap, int *));
                        continue;
                case 'd':
                        base = 10;
                        break;
                case 'm':       /* Print out memory, 2 hex chars per byte */
                        if (is_long)
                                fcp = va_arg(ap, char *);
                        else {
                                if ((cp1 = va_arg(ap, char *)) != NULL)
                                        fcp = (char *)cp1;
                                else
                                        fcp = NULL;
                        }
                        if (!fcp) {
                                for (fcp = (char *)"(NULL)"; *fcp; fcp++)
                                        count += (*putc_func)(cookie, *fcp);
                        } else {
                                while (digits--) {
                                        int u1 = *fcp++ & 0xFF;
                                        count += (*putc_func)(cookie,
                                            hex_val[(u1>>4)& 0xF]);
                                        count += (*putc_func)(cookie,
                                            hex_val[u1& 0xF]);
                                }
                        }
                        continue;
                case 'o':
                        base = 8;
                        break;
                case 'p':
                        is_long = B_TRUE;
                        /* FALLTHRU */
                case 'x':
                        base = 16;
                        break;
                case 's':
                        if (is_long)
                                fcp = va_arg(ap, char *);
                        else {
                                if ((cp1 = va_arg(ap, char *)) != NULL)
                                        fcp = (char *)cp1;
                                else
                                        fcp = NULL;
                        }
                        if (!fcp)
                                fcp = (char *)"(NULL)";
                        while (*fcp) {
                                count += (*putc_func)(cookie, *fcp++);
                                if (digits && --digits == 0)
                                        break;
                        }
                        while (digits > 0) {
                                count += (*putc_func)(cookie, ' ');
                                digits--;
                        }
                        continue;
                case 'u':
                        base = 10;
                        break;
                default:
                        return (count);
                }
                if (is_long)
                        val = va_arg(ap, long);
                else
                        val = va_arg(ap, int);
                if (base == 10 && ch != 'u') {
                        if (val < 0) {
                                count += (*putc_func)(cookie, '-');
                                val = -val;
                        }
                        uval = val;
                } else {
                        if (is_long)
                                uval = val;
                        else
                                uval = (uint_t)val;
                }
                /* Hand overload/restore the register variable 'fmt' */
                cp1 = fmt;
                fmt = A_END(buf);
                *--fmt = '\0';
                do {
                        if (fmt > buf)
                                *--fmt = hex_val[uval % base];
                        if (digits && --digits == 0)
                                break;
                } while (uval /= base);
                if (zero_filled) {
                        while (digits > 0 && fmt > buf) {
                                *--fmt = '0';
                                digits--;
                        }
                }
                while (*fmt)
                        count += (*putc_func)(cookie, *fmt++);
                fmt = cp1;
        }
        return (count);
}

/* PRINTFLIKE2 */
int
mi_mpprintf(MBLKP mp, char *fmt, ...)
{
        va_list ap;
        int     count = -1;

        va_start(ap, fmt);
        if (mp) {
                count = mi_iprintf(fmt, ap, (pfi_t)mi_mpprintf_putc,
                    (char *)mp);
                if (count != -1)
                        (void) mi_mpprintf_putc((char *)mp, '\0');
        }
        va_end(ap);
        return (count);
}

/* PRINTFLIKE2 */
int
mi_mpprintf_nr(MBLKP mp, char *fmt, ...)
{
        va_list ap;
        int     count = -1;

        va_start(ap, fmt);
        if (mp) {
                (void) adjmsg(mp, -1);
                count = mi_iprintf(fmt, ap, (pfi_t)mi_mpprintf_putc,
                    (char *)mp);
                if (count != -1)
                        (void) mi_mpprintf_putc((char *)mp, '\0');
        }
        va_end(ap);
        return (count);
}

int
mi_mpprintf_putc(char *cookie, int ch)
{
        MBLKP   mp = (MBLKP)cookie;

        while (mp->b_cont)
                mp = mp->b_cont;
        if (mp->b_wptr >= mp->b_datap->db_lim) {
                mp->b_cont = allocb(1024, BPRI_HI);
                mp = mp->b_cont;
                if (!mp)
                        return (0);
        }
        *mp->b_wptr++ = (unsigned char)ch;
        return (1);
}

IDP
mi_first_ptr(void **mi_headp)
{
        mi_head_t *mi_head = *(mi_head_t **)mi_headp;
        MI_OP   mi_op;

        mi_op = mi_head->mh_o.mi_o_next;
        if (mi_op && mi_op != &mi_head->mh_o)
                return ((IDP)&mi_op[1]);
        return (NULL);
}

/*
 * Clients can choose to have both module instances and device instances
 * in the same list. Return the first device instance in the list.
 */
IDP
mi_first_dev_ptr(void **mi_headp)
{
        mi_head_t *mi_head = *(mi_head_t **)mi_headp;
        MI_OP   mi_op;

        mi_op = mi_head->mh_o.mi_o_next;
        while ((mi_op != NULL) && (mi_op != &mi_head->mh_o)) {
                if (mi_op->mi_o_isdev)
                        return ((IDP)&mi_op[1]);
                mi_op = mi_op->mi_o_next;
        }
        return (NULL);
}

IDP
mi_next_ptr(void **mi_headp, IDP ptr)
{
        mi_head_t *mi_head = *(mi_head_t **)mi_headp;
        MI_OP   mi_op = ((MI_OP)ptr) - 1;

        if ((mi_op = mi_op->mi_o_next) != NULL && mi_op != &mi_head->mh_o)
                return ((IDP)&mi_op[1]);
        return (NULL);
}

/*
 * Clients can choose to have both module instances and device instances
 * in the same list. Return the next device instance in the list.
 */
IDP
mi_next_dev_ptr(void **mi_headp, IDP ptr)
{
        mi_head_t *mi_head = *(mi_head_t **)mi_headp;
        MI_OP   mi_op = ((MI_OP)ptr) - 1;

        mi_op = mi_op->mi_o_next;
        while ((mi_op != NULL) && (mi_op != &mi_head->mh_o)) {
                if (mi_op->mi_o_isdev)
                        return ((IDP)&mi_op[1]);
                mi_op = mi_op->mi_o_next;
        }
        return (NULL);
}

/*
 * Self clone the device
 * XXX - should we still support clone device
 */
/* ARGSUSED4 */
int
mi_open_comm(void **mi_headp, size_t size, queue_t *q, dev_t *devp,
    int flag, int sflag, cred_t *credp)
{
        int error;
        IDP ptr;

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

        ptr = mi_open_alloc_sleep(size);
        q->q_ptr = WR(q)->q_ptr = ptr;
        error = mi_open_link(mi_headp, ptr, devp, flag, sflag, credp);
        if (error != 0) {
                q->q_ptr = WR(q)->q_ptr = NULL;
                mi_close_free(ptr);
        }
        return (error);
}

IDP
mi_open_alloc_sleep(size_t size)
{
        MI_OP           mi_o;

        if (size > (UINT_MAX - sizeof (MI_O)))
                return (NULL);

        mi_o = (MI_OP)mi_zalloc_sleep(size + sizeof (MI_O));
        mi_o++;
        return ((IDP)mi_o);
}

IDP
mi_open_alloc(size_t size)
{
        MI_OP           mi_o;

        if (size > (UINT_MAX - sizeof (MI_O)))
                return (NULL);

        if ((mi_o = (MI_OP)mi_zalloc(size + sizeof (MI_O))) == NULL)
                return (NULL);
        mi_o++;
        return ((IDP)mi_o);
}

/*
 * MODOPEN means just link in without respect of mi_o_dev.
 * A NULL devp can be used to create a detached instance
 * Otherwise self-clone the device.
 */
/* ARGSUSED3 */
int
mi_open_link(void **mi_headp, IDP ptr, dev_t *devp, int flag, int sflag,
    cred_t *credp)
{
        mi_head_t       *mi_head = *(mi_head_t **)mi_headp;
        MI_OP           insert;
        MI_OP           mi_o;
        dev_t           dev;

        if (mi_head == NULL) {
                char arena_name[50];
                char *head_name;
                ulong_t offset;

                head_name = kobj_getsymname((uintptr_t)mi_headp, &offset);
                if (head_name != NULL && offset == 0) {
                        (void) sprintf(arena_name, "%s_", head_name);
                } else {
                        (void) sprintf(arena_name, "Hex0x%p_",
                            (void *)mi_headp);
                }
                (void) sprintf(strchr(arena_name, '_') + 1, "minor");
                mi_head = (mi_head_t *)mi_zalloc_sleep(sizeof (mi_head_t));
                *mi_headp = (void *)mi_head;
                /* Setup doubly linked list */
                mi_head->mh_o.mi_o_next = &mi_head->mh_o;
                mi_head->mh_o.mi_o_prev = &mi_head->mh_o;
                mi_head->mh_o.mi_o_dev = 0;     /* For asserts only */
                mi_head->mh_arena = (vmem_t *)inet_minor_create(arena_name,
                    INET_MIN_DEV, MAXMIN, KM_SLEEP);
        }
        ASSERT(ptr != NULL);
        mi_o = (MI_OP)ptr;
        mi_o--;

        if (sflag == MODOPEN) {
                devp = NULL;
                /*
                 * Set device number to MAXMIN + incrementing number.
                 */
                dev = MAXMIN + ++mi_head->mh_module_dev;
                /* check for wraparound */
                if (dev <= MAXMIN) {
                        dev = MAXMIN + 1;
                        mi_head->mh_module_dev = 1;
                }
        } else if (devp == NULL) {
                /* Detached open */
                dev = (dev_t)OPENFAIL;
        } else if ((dev = inet_minor_alloc(mi_head->mh_arena)) == 0) {
                return (EBUSY);
        }

        mi_o->mi_o_dev = dev;
        insert = (&mi_head->mh_o);
        mi_o->mi_o_next = insert;
        insert->mi_o_prev->mi_o_next = mi_o;
        mi_o->mi_o_prev = insert->mi_o_prev;
        insert->mi_o_prev = mi_o;

        if (sflag == MODOPEN)
                mi_o->mi_o_isdev = B_FALSE;
        else
                mi_o->mi_o_isdev = B_TRUE;

        if (devp)
                *devp = makedevice(getemajor(*devp), (minor_t)dev);
        return (0);
}

uint8_t *
mi_offset_param(mblk_t *mp, size_t offset, size_t len)
{
        size_t  msg_len;

        if (!mp)
                return (NULL);
        msg_len = mp->b_wptr - mp->b_rptr;
        if (msg_len == 0 || offset > msg_len || len > msg_len ||
            (offset + len) > msg_len || len == 0)
                return (NULL);
        return (&mp->b_rptr[offset]);
}

uint8_t *
mi_offset_paramc(mblk_t *mp, size_t offset, size_t len)
{
        uint8_t *param;

        for (; mp; mp = mp->b_cont) {
                int type = mp->b_datap->db_type;
                if (datamsg(type)) {
                        if (param = mi_offset_param(mp, offset, len))
                                return (param);
                        if (offset < mp->b_wptr - mp->b_rptr)
                                break;
                        offset -= mp->b_wptr - mp->b_rptr;
                }
        }
        return (NULL);
}

int
mi_sprintf(char *buf, char *fmt, ...)
{
        va_list ap;
        int     count = -1;
        va_start(ap, fmt);
        if (buf) {
                count = mi_iprintf(fmt, ap, (pfi_t)mi_sprintf_putc,
                    (char *)&buf);
                if (count != -1)
                        (void) mi_sprintf_putc((char *)&buf, '\0');
        }
        va_end(ap);
        return (count);
}

/* Used to count without writing data */
/* ARGSUSED1 */
static int
mi_sprintf_noop(char *cookie, int ch)
{
        char    **cpp = (char **)cookie;

        (*cpp)++;
        return (1);
}

int
mi_sprintf_putc(char *cookie, int ch)
{
        char    **cpp = (char **)cookie;

        **cpp = (char)ch;
        (*cpp)++;
        return (1);
}

int
mi_strcmp(const char *cp1, const char *cp2)
{
        while (*cp1++ == *cp2++) {
                if (!cp2[-1])
                        return (0);
        }
        return ((uint_t)cp2[-1]  & 0xFF) - ((uint_t)cp1[-1] & 0xFF);
}

size_t
mi_strlen(const char *str)
{
        const char *cp = str;

        while (*cp != '\0')
                cp++;
        return ((int)(cp - str));
}

int
mi_strlog(queue_t *q, char level, ushort_t flags, char *fmt, ...)
{
        va_list ap;
        char    buf[200];
        char    *alloc_buf = buf;
        int     count = -1;
        char    *cp;
        short   mid;
        int     ret;
        short   sid;

        sid = 0;
        mid = 0;
        if (q != NULL) {
                mid = q->q_qinfo->qi_minfo->mi_idnum;
        }

        /* Find out how many bytes we need and allocate if necesary */
        va_start(ap, fmt);
        cp = buf;
        count = mi_iprintf(fmt, ap, mi_sprintf_noop, (char *)&cp);
        if (count > sizeof (buf) &&
            !(alloc_buf = mi_alloc((uint_t)count + 2, BPRI_MED))) {
                va_end(ap);
                return (-1);
        }
        va_end(ap);

        va_start(ap, fmt);
        cp = alloc_buf;
        count = mi_iprintf(fmt, ap, mi_sprintf_putc, (char *)&cp);
        if (count != -1)
                (void) mi_sprintf_putc((char *)&cp, '\0');
        else
                alloc_buf[0] = '\0';
        va_end(ap);

        ret = strlog(mid, sid, level, flags, alloc_buf);
        if (alloc_buf != buf)
                mi_free(alloc_buf);
        return (ret);
}

long
mi_strtol(const char *str, char **ptr, int base)
{
        const char *cp;
        int     digits;
        long    value;
        boolean_t       is_negative;

        cp = str;
        while (*cp == ' ' || *cp == '\t' || *cp == '\n')
                cp++;
        is_negative = (*cp == '-');
        if (is_negative)
                cp++;
        if (base == 0) {
                base = 10;
                if (*cp == '0') {
                        base = 8;
                        cp++;
                        if (*cp == 'x' || *cp == 'X') {
                                base = 16;
                                cp++;
                        }
                }
        }
        value = 0;
        for (; *cp != '\0'; cp++) {
                if (*cp >= '0' && *cp <= '9')
                        digits = *cp - '0';
                else if (*cp >= 'a' && *cp <= 'f')
                        digits = *cp - 'a' + 10;
                else if (*cp >= 'A' && *cp <= 'F')
                        digits = *cp - 'A' + 10;
                else
                        break;
                if (digits >= base)
                        break;
                value = (value * base) + digits;
        }
        /* Note: we cast away const here deliberately */
        if (ptr != NULL)
                *ptr = (char *)cp;
        if (is_negative)
                value = -value;
        return (value);
}

/*
 *              mi_timer mechanism.
 *
 * Each timer is represented by a timer mblk and a (streams) queue. When the
 * timer fires the timer mblk will be put on the associated streams queue
 * so that the streams module can process the timer even in its service
 * procedure.
 *
 * The interface consists of 4 entry points:
 *      mi_timer_alloc          - create a timer mblk
 *      mi_timer_free           - free a timer mblk
 *      mi_timer                - start, restart, stop, or move the
 *                                timer to a different queue
 *      mi_timer_valid          - called by streams module to verify that
 *                                the timer did indeed fire.
 */




/*
 * Start, restart, stop, or move the timer to a new queue.
 * If "tim" is -2 the timer is moved to a different queue.
 * If "tim" is -1 the timer is stopped.
 * Otherwise, the timer is stopped if it is already running, and
 * set to fire tim milliseconds from now.
 */

void
mi_timer(queue_t *q, MBLKP mp, clock_t tim)
{
        MTBP    mtb;
        int     state;

        ASSERT(tim >= -2);
        if (!q || !mp || (mp->b_rptr - mp->b_datap->db_base) != sizeof (MTB))
                return;
        mtb = (MTBP)mp->b_datap->db_base;
        ASSERT(mp->b_datap->db_type == M_PCSIG);
        if (tim >= 0) {
                mtb->mtb_q = q;
                state = mtb->mtb_state;
                tim = MSEC_TO_TICK(tim);
                if (state == TB_RUNNING) {
                        if (untimeout(mtb->mtb_tid) < 0) {
                                /* Message has already been putq */
                                ASSERT(mtb->mtb_q->q_first == mp ||
                                    mp->b_prev || mp->b_next);
                                mtb->mtb_state = TB_RESCHED;
                                mtb->mtb_time_left = tim;
                                /* mi_timer_valid will start timer */
                                return;
                        }
                } else if (state != TB_IDLE) {
                        ASSERT(state != TB_TO_BE_FREED);
                        if (state == TB_CANCELLED) {
                                ASSERT(mtb->mtb_q->q_first == mp ||
                                    mp->b_prev || mp->b_next);
                                mtb->mtb_state = TB_RESCHED;
                                mtb->mtb_time_left = tim;
                                /* mi_timer_valid will start timer */
                                return;
                        }
                        if (state == TB_RESCHED) {
                                ASSERT(mtb->mtb_q->q_first == mp ||
                                    mp->b_prev || mp->b_next);
                                mtb->mtb_time_left = tim;
                                /* mi_timer_valid will start timer */
                                return;
                        }
                }
                mtb->mtb_state = TB_RUNNING;
                mtb->mtb_tid = timeout(mi_timer_fire, mtb, tim);
                return;
        }
        switch (tim) {
        case -1:
                mi_timer_stop(mp);
                break;
        case -2:
                mi_timer_move(q, mp);
                break;
        }
}

/*
 * Allocate an M_PCSIG timer message. The space between db_base and
 * b_rptr is used by the mi_timer mechanism, and after b_rptr there are
 * "size" bytes that the caller can use for its own purposes.
 *
 * Note that db_type has to be a priority message since otherwise
 * the putq will not cause the service procedure to run when
 * there is flow control.
 */
MBLKP
mi_timer_alloc(size_t size)
{
        MBLKP   mp;
        MTBP    mtb;

        if ((mp = allocb(size + sizeof (MTB), BPRI_HI)) != NULL) {
                mp->b_datap->db_type = M_PCSIG;
                mtb = (MTBP)mp->b_datap->db_base;
                mp->b_rptr = (uchar_t *)&mtb[1];
                mp->b_wptr = mp->b_rptr + size;
                mtb->mtb_state = TB_IDLE;
                mtb->mtb_mp = mp;
                mtb->mtb_q = NULL;
                return (mp);
        }
        return (NULL);
}

/*
 * timeout() callback function.
 * Put the message on the current queue.
 * If the timer is stopped or moved to a different queue after
 * it has fired then mi_timer() and mi_timer_valid() will clean
 * things up.
 */
static void
mi_timer_fire(void *ptr)
{
        MTBP mtb = ptr;

        ASSERT(mtb == (MTBP)mtb->mtb_mp->b_datap->db_base);
        ASSERT(mtb->mtb_mp->b_datap->db_type == M_PCSIG);
        (void) putq(mtb->mtb_q, mtb->mtb_mp);
}

/*
 * Logically free a timer mblk (that might have a pending timeout().)
 * If the timer has fired and the mblk has been put on the queue then
 * mi_timer_valid will free the mblk.
 */

void
mi_timer_free(MBLKP mp)
{
        MTBP    mtb;
        int     state;

        if (!mp || (mp->b_rptr - mp->b_datap->db_base) != sizeof (MTB))
                return;
        mtb = (MTBP)mp->b_datap->db_base;
        state = mtb->mtb_state;
        if (state == TB_RUNNING) {
                if (untimeout(mtb->mtb_tid) < 0) {
                        /* Message has already been putq */
                        ASSERT(mtb->mtb_q->q_first == mp ||
                            mp->b_prev || mp->b_next);
                        mtb->mtb_state = TB_TO_BE_FREED;
                        /* mi_timer_valid will free the mblk */
                        return;
                }
        } else if (state != TB_IDLE) {
                /* Message has already been putq */
                ASSERT(mtb->mtb_q->q_first == mp ||
                    mp->b_prev || mp->b_next);
                ASSERT(state != TB_TO_BE_FREED);
                mtb->mtb_state = TB_TO_BE_FREED;
                /* mi_timer_valid will free the mblk */
                return;
        }
        ASSERT(mtb->mtb_q ==  NULL || mtb->mtb_q->q_first != mp);
        freemsg(mp);
}

/*
 * Called from mi_timer(,,-2)
 */
void
mi_timer_move(queue_t *q, MBLKP mp)
{
        MTBP    mtb;
        clock_t tim;

        if (!q || !mp || (mp->b_rptr - mp->b_datap->db_base) != sizeof (MTB))
                return;

        mtb = (MTBP)mp->b_datap->db_base;
        /*
         * Need to untimeout and restart to make
         * sure that the mblk is not about to be putq on the old queue
         * by mi_timer_fire.
         */
        if (mtb->mtb_state == TB_RUNNING) {
                if ((tim = untimeout(mtb->mtb_tid)) < 0) {
                        /*
                         * Message has already been putq. Move from old queue
                         * to new queue.
                         */
                        ASSERT(mtb->mtb_q->q_first == mp ||
                            mp->b_prev || mp->b_next);
                        rmvq(mtb->mtb_q, mp);
                        ASSERT(mtb->mtb_q->q_first != mp &&
                            mp->b_prev == NULL && mp->b_next == NULL);
                        mtb->mtb_q = q;
                        (void) putq(mtb->mtb_q, mp);
                        return;
                }
                mtb->mtb_q = q;
                mtb->mtb_state = TB_RUNNING;
                mtb->mtb_tid = timeout(mi_timer_fire, mtb, tim);
        } else if (mtb->mtb_state != TB_IDLE) {
                ASSERT(mtb->mtb_state != TB_TO_BE_FREED);
                /*
                 * Message is already sitting on queue. Move to new queue.
                 */
                ASSERT(mtb->mtb_q->q_first == mp ||
                    mp->b_prev || mp->b_next);
                rmvq(mtb->mtb_q, mp);
                ASSERT(mtb->mtb_q->q_first != mp &&
                    mp->b_prev == NULL && mp->b_next == NULL);
                mtb->mtb_q = q;
                (void) putq(mtb->mtb_q, mp);
        } else
                mtb->mtb_q = q;
}

/*
 * Called from mi_timer(,,-1)
 */
void
mi_timer_stop(MBLKP mp)
{
        MTBP    mtb;
        int     state;

        if (!mp || (mp->b_rptr - mp->b_datap->db_base) != sizeof (MTB))
                return;

        mtb = (MTBP)mp->b_datap->db_base;
        state = mtb->mtb_state;
        if (state == TB_RUNNING) {
                if (untimeout(mtb->mtb_tid) < 0) {
                        /* Message has already been putq */
                        ASSERT(mtb->mtb_q->q_first == mp ||
                            mp->b_prev || mp->b_next);
                        mtb->mtb_state = TB_CANCELLED;
                } else {
                        mtb->mtb_state = TB_IDLE;
                }
        } else if (state == TB_RESCHED) {
                ASSERT(mtb->mtb_q->q_first == mp ||
                    mp->b_prev || mp->b_next);
                mtb->mtb_state = TB_CANCELLED;
        }
}

/*
 * The user of the mi_timer mechanism is required to call mi_timer_valid() for
 * each M_PCSIG message processed in the service procedures.
 * mi_timer_valid will return "true" if the timer actually did fire.
 */

boolean_t
mi_timer_valid(MBLKP mp)
{
        MTBP    mtb;
        int     state;

        if (!mp || (mp->b_rptr - mp->b_datap->db_base) != sizeof (MTB) ||
            mp->b_datap->db_type != M_PCSIG)
                return (B_FALSE);
        mtb = (MTBP)mp->b_datap->db_base;
        state = mtb->mtb_state;
        if (state != TB_RUNNING) {
                ASSERT(state != TB_IDLE);
                if (state == TB_TO_BE_FREED) {
                        /*
                         * mi_timer_free was called after the message
                         * was putq'ed.
                         */
                        freemsg(mp);
                        return (B_FALSE);
                }
                if (state == TB_CANCELLED) {
                        /* The timer was stopped after the mblk was putq'ed */
                        mtb->mtb_state = TB_IDLE;
                        return (B_FALSE);
                }
                if (state == TB_RESCHED) {
                        /*
                         * The timer was stopped and then restarted after
                         * the mblk was putq'ed.
                         * mtb_time_left contains the number of ticks that
                         * the timer was restarted with.
                         */
                        mtb->mtb_state = TB_RUNNING;
                        mtb->mtb_tid = timeout(mi_timer_fire,
                            mtb, mtb->mtb_time_left);
                        return (B_FALSE);
                }
        }
        mtb->mtb_state = TB_IDLE;
        return (B_TRUE);
}

static void
mi_tpi_addr_and_opt(MBLKP mp, char *addr, t_scalar_t addr_length,
    char *opt, t_scalar_t opt_length)
{
        struct T_unitdata_ind   *tudi;

        /*
         * This code is used more than just for unitdata ind
         * (also for T_CONN_IND and T_CONN_CON) and
         * relies on correct functioning on the happy
         * coincidence that the address and option buffers
         * represented by length/offset in all these primitives
         * are isomorphic in terms of offset from start of data
         * structure
         */
        tudi = (struct T_unitdata_ind *)mp->b_rptr;
        tudi->SRC_offset = (t_scalar_t)(mp->b_wptr - mp->b_rptr);
        tudi->SRC_length = addr_length;
        if (addr_length > 0) {
                bcopy(addr, (char *)mp->b_wptr, addr_length);
                mp->b_wptr += addr_length;
        }
        tudi->OPT_offset = (t_scalar_t)(mp->b_wptr - mp->b_rptr);
        tudi->OPT_length = opt_length;
        if (opt_length > 0) {
                bcopy(opt, (char *)mp->b_wptr, opt_length);
                mp->b_wptr += opt_length;
        }
}

MBLKP
mi_tpi_conn_con(MBLKP trailer_mp, char *src, t_scalar_t src_length, char *opt,
    t_scalar_t opt_length)
{
        size_t  len;
        MBLKP   mp;

        len = sizeof (struct T_conn_con) + src_length + opt_length;
        if ((mp = mi_tpi_trailer_alloc(trailer_mp, len, T_CONN_CON)) != NULL) {
                mp->b_wptr = &mp->b_rptr[sizeof (struct T_conn_con)];
                mi_tpi_addr_and_opt(mp, src, src_length, opt, opt_length);
        }
        return (mp);
}

MBLKP
mi_tpi_conn_ind(MBLKP trailer_mp, char *src, t_scalar_t src_length, char *opt,
    t_scalar_t opt_length, t_scalar_t seqnum)
{
        size_t  len;
        MBLKP   mp;

        len = sizeof (struct T_conn_ind) + src_length + opt_length;
        if ((mp = mi_tpi_trailer_alloc(trailer_mp, len, T_CONN_IND)) != NULL) {
                mp->b_wptr = &mp->b_rptr[sizeof (struct T_conn_ind)];
                mi_tpi_addr_and_opt(mp, src, src_length, opt, opt_length);
                ((struct T_conn_ind *)mp->b_rptr)->SEQ_number = seqnum;
                mp->b_datap->db_type = M_PROTO;
        }
        return (mp);
}

MBLKP
mi_tpi_extconn_ind(MBLKP trailer_mp, char *src, t_scalar_t src_length,
    char *opt, t_scalar_t opt_length, char *dst, t_scalar_t dst_length,
    t_scalar_t seqnum)
{
        size_t  len;
        MBLKP   mp;

        len = sizeof (struct T_extconn_ind) + src_length + opt_length +
            dst_length;
        if ((mp = mi_tpi_trailer_alloc(trailer_mp, len, T_EXTCONN_IND)) !=
            NULL) {
                mp->b_wptr = &mp->b_rptr[sizeof (struct T_extconn_ind)];
                mi_tpi_addr_and_opt(mp, src, src_length, opt, opt_length);
                ((struct T_extconn_ind *)mp->b_rptr)->DEST_length = dst_length;
                ((struct T_extconn_ind *)mp->b_rptr)->DEST_offset =
                    (t_scalar_t)(mp->b_wptr - mp->b_rptr);
                if (dst_length > 0) {
                        bcopy(dst, (char *)mp->b_wptr, dst_length);
                        mp->b_wptr += dst_length;
                }
                ((struct T_extconn_ind *)mp->b_rptr)->SEQ_number = seqnum;
                mp->b_datap->db_type = M_PROTO;
        }
        return (mp);
}

MBLKP
mi_tpi_discon_ind(MBLKP trailer_mp, t_scalar_t reason, t_scalar_t seqnum)
{
        MBLKP   mp;
        struct T_discon_ind     *tdi;

        if ((mp = mi_tpi_trailer_alloc(trailer_mp,
            sizeof (struct T_discon_ind), T_DISCON_IND)) != NULL) {
                tdi = (struct T_discon_ind *)mp->b_rptr;
                tdi->DISCON_reason = reason;
                tdi->SEQ_number = seqnum;
        }
        return (mp);
}

/*
 * Allocate and fill in a TPI err ack packet using the 'mp' passed in
 * for the 'error_prim' context as well as sacrifice.
 */
MBLKP
mi_tpi_err_ack_alloc(MBLKP mp, t_scalar_t tlierr, int unixerr)
{
        struct T_error_ack      *teackp;
        t_scalar_t error_prim;

        if (!mp)
                return (NULL);
        error_prim = ((TPRIMP)mp->b_rptr)->type;
        if ((mp = tpi_ack_alloc(mp, sizeof (struct T_error_ack),
            M_PCPROTO, T_ERROR_ACK)) != NULL) {
                teackp = (struct T_error_ack *)mp->b_rptr;
                teackp->ERROR_prim = error_prim;
                teackp->TLI_error = tlierr;
                teackp->UNIX_error = unixerr;
        }
        return (mp);
}

MBLKP
mi_tpi_ok_ack_alloc_extra(MBLKP mp, int extra)
{
        t_scalar_t correct_prim;

        if (!mp)
                return (NULL);
        correct_prim = ((TPRIMP)mp->b_rptr)->type;
        if ((mp = tpi_ack_alloc(mp, sizeof (struct T_ok_ack) + extra,
            M_PCPROTO, T_OK_ACK)) != NULL) {
                ((struct T_ok_ack *)mp->b_rptr)->CORRECT_prim = correct_prim;
                mp->b_wptr -= extra;
        }
        return (mp);
}

MBLKP
mi_tpi_ok_ack_alloc(MBLKP mp)
{
        return (mi_tpi_ok_ack_alloc_extra(mp, 0));
}

MBLKP
mi_tpi_ordrel_ind(void)
{
        MBLKP   mp;

        if ((mp = allocb(sizeof (struct T_ordrel_ind), BPRI_HI)) != NULL) {
                mp->b_datap->db_type = M_PROTO;
                ((struct T_ordrel_ind *)mp->b_rptr)->PRIM_type = T_ORDREL_IND;
                mp->b_wptr += sizeof (struct T_ordrel_ind);
        }
        return (mp);
}

static MBLKP
mi_tpi_trailer_alloc(MBLKP trailer_mp, size_t size, t_scalar_t type)
{
        MBLKP   mp;

        if ((mp = allocb(size, BPRI_MED)) != NULL) {
                mp->b_cont = trailer_mp;
                mp->b_datap->db_type = M_PROTO;
                ((union T_primitives *)mp->b_rptr)->type = type;
                mp->b_wptr += size;
        }
        return (mp);
}

MBLKP
mi_tpi_uderror_ind(char *dest, t_scalar_t dest_length, char *opt,
    t_scalar_t opt_length, t_scalar_t error)
{
        size_t  len;
        MBLKP   mp;
        struct T_uderror_ind    *tudei;

        len = sizeof (struct T_uderror_ind) + dest_length + opt_length;
        if ((mp = allocb(len, BPRI_HI)) != NULL) {
                mp->b_datap->db_type = M_PROTO;
                tudei = (struct T_uderror_ind *)mp->b_rptr;
                tudei->PRIM_type = T_UDERROR_IND;
                tudei->ERROR_type = error;
                mp->b_wptr = &mp->b_rptr[sizeof (struct T_uderror_ind)];
                mi_tpi_addr_and_opt(mp, dest, dest_length, opt, opt_length);
        }
        return (mp);
}

IDP
mi_zalloc(size_t size)
{
        IDP     ptr;

        if (ptr = mi_alloc(size, BPRI_LO))
                bzero(ptr, size);
        return (ptr);
}

IDP
mi_zalloc_sleep(size_t size)
{
        IDP     ptr;

        if (ptr = mi_alloc_sleep(size, BPRI_LO))
                bzero(ptr, size);
        return (ptr);
}