#include <sys/types.h>
#include <sys/systm.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ethernet.h>
#include <sys/cmn_err.h>
#include <sys/stream.h>
#include <sys/dlpi.h>
#include <sys/tihdr.h>
#include <sys/conf.h>
#include <sys/kmem.h>

#include <sys/socket.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/route.h>
#include <sys/sockio.h>
#include <netinet/in.h>

#include <inet/common.h>
#include <inet/mi.h>
#include <inet/mib2.h>
#include <inet/nd.h>
#include <inet/arp.h>
#include <netinet/ip6.h>
#include <inet/ip.h>

/*#define	C_GRE_DEBUG	0*/
#include <gre.h>

#if	!defined(IPV4_ADDR_LEN)
#define	IPV4_ADDR_LEN	(4)

#endif

void	c_gre_wput(queue_t *q, mblk_t  *mp);
void	c_gre_rput(queue_t *q, mblk_t  *mp);
void	c_gre_wsrv(queue_t *q);
void	c_gre_rsrv(queue_t *q);
int     c_gre_open(queue_t *, dev_t *, int, int, cred_t *);
int     c_gre_close(queue_t *, int, cred_t *);

static mblk_t   *gre_realloc_mblk(queue_t *, mblk_t *, size_t, mblk_t *,
                    boolean_t);


static	struct	module_info info = {
	C_GRE_MODID,
	C_GRE_NAME,
	1,
	INFPSZ,
	65536,
	1024
};

static	struct	qinit c_grewinit = {
	(pfi_t)c_gre_wput,
	(pfi_t)c_gre_wsrv,
	NULL,
	NULL,
	NULL,
	&info,
	NULL
};

static	struct	qinit c_grerinit = {
	(pfi_t)c_gre_rput,
	(pfi_t)c_gre_rsrv,
	c_gre_open,
	c_gre_close,
	NULL,
	&info,
	NULL
};

struct streamtab c_gre_info = {
        &c_grerinit,              /* read side queue init */
        &c_grewinit,              /* write side queue init */
        NULL,                    /* mux read side init */
        NULL                     /* mux write side init */
};


static struct fmodsw c_gre_fmodsw = {
        C_GRE_NAME,
        &c_gre_info,
        (D_NEW | D_MP | D_MTQPAIR | D_MTPUTSHARED)
};


static struct modlstrmod modlstrmod = {
        &mod_strmodops,
        "cisco GRE",
        &c_gre_fmodsw
};
         
static	struct	modlinkage	modlinkage = {
	MODREV_1,
	(void*)&modlstrmod,
	NULL
};

static	gre_header_t	gre_header;

struct	gre_s {
        struct gre_s    *gre_next;
	int		gre_flags;
	t_uscalar_t     gre_state;
	uint32_t	gre_mtu;
	t_uscalar_t	gre_ppa;
	in_addr_t	gre_laddr;
	in_addr_t	gre_faddr;
	ipha_t		gre_ipha;
	int		gre_extra_offset;
	mblk_t		*gre_iocmp;
};

dl_info_ack_t infoack = {
        DL_INFO_ACK,    
        4196,
        0,
        0,
        DL_OTHER,       
        0,              
        DL_UNATTACHED,  
        0,              
        DL_CLDLS,       
        0,              
        0,              
        0,              
        0,              
        DL_STYLE2,      
        0,              
        DL_VERSION_2,   
        0,              
        0,              
        0               
};
dl_bind_ack_t bindack = {
        DL_BIND_ACK,
        0,
        0,
        0,
        0,
        0
};


int
_init(void)
{
int	i;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "_init()\n");
#endif
    i = mod_install(&modlinkage);
    bzero(&gre_header, sizeof(gre_header));
    gre_header.proto_type = htons(0x0800);	/* Proto type IP */
    return(i);
}

int
_info(struct modinfo *modinfop)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "_info()\n");
#endif
    return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
int i;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "_fini()\n");
#endif
    i = mod_remove(&modlinkage);
    return(i);
}

gre_sendokack(queue_t *q, mblk_t *mp, t_uscalar_t prim)
{
        dl_ok_ack_t *dlok;

        if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_ok_ack_t), mp,
            B_FALSE)) == NULL) {
                return (ENOMEM);
        }
        dlok = (dl_ok_ack_t *)mp->b_rptr;
        dlok->dl_primitive = DL_OK_ACK;
        dlok->dl_correct_primitive = prim;
        mp->b_datap->db_type = M_PCPROTO;
        qreply(q, mp);
        return (0);
}

static void
c_gre_wput_dlpi_other(queue_t *q, mblk_t *mp)
{
t_uscalar_t	prim = *((t_uscalar_t *)mp->b_rptr);
struct gre_s	*gre  = q->q_ptr;
t_uscalar_t dl_err = DL_UNSUPPORTED;
t_uscalar_t dl_errno = 0;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_dlpi_other(): %d\n", prim);
#endif
    switch(prim) {
    case DL_PHYS_ADDR_REQ: {
        dl_phys_addr_ack_t *dpa;

#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_PHYS_ADDR_REQ\n");
#endif
        if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_phys_addr_ack_t),
            mp, B_FALSE)) == NULL) {
                return;
        }

        dpa = (dl_phys_addr_ack_t *)mp->b_rptr;
        dpa->dl_primitive = DL_PHYS_ADDR_ACK;
        dpa->dl_addr_length = IPV4_ADDR_LEN;
        dpa->dl_addr_offset = 0;
        mp->b_datap->db_type = M_PCPROTO;
        qreply(q, mp);
        return;
	}
	break;
    case DL_BIND_REQ: {
        dl_bind_req_t *bind_req;
        t_uscalar_t dl_sap = 0;

#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_BIND_REQ\n");
#endif
	bind_req = (dl_bind_req_t *)mp->b_rptr;
        dl_sap = bind_req->dl_sap;
	gre->gre_state = DL_IDLE;
	
        *(dl_bind_ack_t *)mp->b_rptr = bindack;
        ((dl_bind_ack_t *)mp->b_rptr)->dl_sap = dl_sap;
        mp->b_datap->db_type = M_PCPROTO;
        qreply(q, mp);
        return;
	}
	break;
    case DL_UNBIND_REQ: {
#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_UNBIND_REQ\n");
#endif
        if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_ok_ack_t), mp,
            B_FALSE)) == NULL) {
                return;
        }
        gre->gre_state = DL_UNBOUND;
        (void) gre_sendokack(q, mp, prim);
        return;
	}
	break;
    case DL_ATTACH_REQ: {
	dl_attach_req_t *dla;
#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_ATTACH_REQ\n");
#endif

        if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_ok_ack_t), mp,
                B_TRUE)) == NULL) {
                return;
        }

        dla = (dl_attach_req_t *)mp->b_rptr;

        if (gre->gre_state != DL_UNATTACHED) {
                dl_err = DL_OUTSTATE;
                break;
        }
        gre->gre_ppa = dla->dl_ppa;
	gre->gre_state = DL_UNBOUND;
        (void) gre_sendokack(q, mp, prim);
        return;
	}
	break;
    case DL_DETACH_REQ: {
#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_DETACH_REQ\n");
#endif

        if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_ok_ack_t), mp,
            B_FALSE)) == NULL) {
                return;
        }
	gre->gre_state = DL_UNATTACHED;
	(void) gre_sendokack(q, mp, prim);
	return;
        }
        break;
    case DL_INFO_REQ: {
	dl_info_ack_t *dinfo;
#ifdef	C_GRE_DEBUG
	cmn_err(CE_CONT, "c_gre_dlpi_other(): DL_INFO_REQ\n");
#endif
	if ((mp = gre_realloc_mblk(q, mp, sizeof (dl_info_ack_t), mp,
		B_FALSE)) == NULL) {
	    return;
	}
	mp->b_datap->db_type = M_PCPROTO;
	dinfo = (dl_info_ack_t *)mp->b_rptr;

	*dinfo = infoack;
	dinfo->dl_current_state = gre->gre_state;
	dinfo->dl_max_sdu = gre->gre_mtu;
	dinfo->dl_addr_length = IPV4_ADDR_LEN;
	qreply(q, mp);
	return;
	}
	break;	
    }
}

int
gre_wdata(queue_t *q, mblk_t *mp)
{
struct gre_s	*gre = q->q_ptr;
ipha_t 		*outer_ipha, *inner_ipha;
gre_header_t	*outer_gre;
mblk_t		*newmp;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_wdata():\n");
#endif
        if (gre->gre_state != DL_IDLE ) {
		freemsg(mp);
		return(0);
        }

	ASSERT((mp->b_wptr - mp->b_rptr) >= sizeof (ipha_t));

	inner_ipha = (ipha_t *)mp->b_rptr;

	if (inner_ipha->ipha_dst == gre->gre_ipha.ipha_dst) {
		/* avoid loops */
		freemsg(mp);
		return(0);
	}
	
	if ((mp->b_rptr - mp->b_datap->db_base) < sizeof (ipha_t) + sizeof(gre_header_t)) {
		/* must be allocated */
                newmp = allocb(sizeof (ipha_t) + sizeof(gre_header_t) +
			gre->gre_extra_offset,
                            BPRI_HI);
                if (newmp == NULL) {
                        freemsg(mp);
                        return (0);
                }
                newmp->b_cont = mp;
                mp = newmp;
                mp->b_wptr = mp->b_datap->db_lim;
                mp->b_rptr = mp->b_wptr - sizeof (ipha_t) - sizeof(gre_header_t);
	} else {
		mp->b_rptr -= sizeof (ipha_t) + sizeof(gre_header_t);
	}
	outer_ipha = (ipha_t *)mp->b_rptr;
	/* copy */
	*outer_ipha = gre->gre_ipha;
	outer_ipha->ipha_length = htons(ntohs(inner_ipha->ipha_length) +
		sizeof (ipha_t) + sizeof(gre_header_t));

	outer_gre = (gre_header_t*)(outer_ipha + 1);
	*outer_gre = gre_header;

	putnext(q, mp);
	return(0);
drop:
	freemsg(mp);
	return(0);
}

int
c_gre_wput_dlpi(queue_t *q, mblk_t *mp)
{
int		error = 0;
t_uscalar_t	prim = *((t_uscalar_t *)mp->b_rptr);
mblk_t		*mp1;
struct gre_s	*gre = q->q_ptr;
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_wput_dlpi(): %d\n", prim);
#endif

    switch(prim) {
    case DL_UNITDATA_REQ:
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_dlpi(): DL_UNITDATA_REQ\n");
#endif
        if (!canputnext(q)) {
                (void) putbq(q, mp);
                return (ENOMEM); /* to get service proc to stop */
        }
        if (gre->gre_state != DL_IDLE) {
		putnext(q, mp);
                break;
        }
	mp1 = mp->b_cont;
	freeb(mp);
	if ( mp1 == NULL ) {
	    break;
	}
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_wput_dlpi(): PASS data down\n");
#endif
	error = gre_wdata(q, mp1);
	break;
    default:
	qwriter(q, mp, c_gre_wput_dlpi_other, PERIM_INNER);
	break;
    }
    return(error);
}

int
gre_send_bind_req(queue_t *q, mblk_t *orig_mp)
{
struct  gre_s           *gre = q->q_ptr;
size_t			size;
mblk_t			*mp;
char			*cp;
ipa_conn_t		*ipa;
struct  		T_bind_req *tbr;

    if ( (gre->gre_flags & TUN_SRC) == 0 ) return(EINVAL);
    size = sizeof (ipa_conn_t);
    if ((mp = gre_realloc_mblk(q, NULL, size + sizeof (struct T_bind_req) +
        1, orig_mp, B_FALSE)) == NULL) {
            return (ENOMEM);
    }
    if ((mp->b_cont = gre_realloc_mblk(q, NULL, sizeof (ire_t), orig_mp,
        B_FALSE)) == NULL) {
            freeb(mp);
            return (ENOMEM);
    }
    mp->b_cont->b_datap->db_type = IRE_DB_REQ_TYPE;
    tbr = (struct T_bind_req *)mp->b_rptr;
    tbr->CONIND_number = 0;
    tbr->PRIM_type = T_BIND_REQ;
    tbr->ADDR_length = size;
    tbr->ADDR_offset = sizeof (struct T_bind_req);
    cp = (char *)&tbr[1];
    ipa = (ipa_conn_t *)cp;
    bzero(ipa, sizeof (ipa_conn_t));
    ipa->ac_laddr = gre->gre_laddr;
    ipa->ac_faddr = gre->gre_faddr; 
    ipa->ac_fport = 0;
    ipa->ac_lport = 0;
    *(cp + size) = (uchar_t)IPPROTO_GRE;
    mp->b_datap->db_type = M_PCPROTO;

    gre->gre_flags |= TUN_BIND_SENT;
    putnext(WR(q), mp);
    return (0);
}

void
c_gre_sparam(queue_t *q, mblk_t *mp)
{
mblk_t			*mp1;
struct	iftun_req	*ta;
struct	sockaddr_in   	*sin;
struct	gre_s		*gre = q->q_ptr;
int			uerr = 0;
struct  iocblk   *iocp = (struct iocblk *)(mp->b_rptr);

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_sparam()\n");
#endif
        if (gre->gre_state  == DL_IDLE) {
                uerr = EAGAIN;
                goto nak;
        }

        mp1 = mp->b_cont;
        if (mp1 == NULL) {
                uerr = EPROTO;
                goto nak;
        }

        mp1 = mp1->b_cont;
        if (mp1 == NULL) {
                uerr = EPROTO;
                goto nak;
        }
        if (mp1->b_wptr - mp1->b_rptr != sizeof (struct iftun_req)) {
                uerr = EPROTO;
                goto nak;
        }
	if ( gre->gre_iocmp ) {
		uerr = EBUSY;
		goto nak;
	}
        ta = (struct iftun_req *)mp1->b_rptr;
        if (ta->ifta_flags & IFTUN_SRC) {
                switch (ta->ifta_saddr.ss_family) {
                case AF_INET:
                        sin = (struct sockaddr_in *)&ta->ifta_saddr;
                        if ((sin->sin_addr.s_addr == INADDR_ANY) ||
                            (sin->sin_addr.s_addr == 0xffffffff)) {
                                uerr = EADDRNOTAVAIL;
                                goto nak;
                        }
                        gre->gre_laddr = sin->sin_addr.s_addr;
			gre->gre_ipha.ipha_src = sin->sin_addr.s_addr;
                        break;
                default:
                        uerr = EAFNOSUPPORT;
                        goto nak;
		}
		gre->gre_flags |= TUN_SRC;
	}
        if (ta->ifta_flags & IFTUN_DST) {
                if (ta->ifta_saddr.ss_family == AF_INET) {
                        sin = (struct sockaddr_in *)&ta->ifta_daddr;
                        if ((sin->sin_addr.s_addr == 0) ||
                            (sin->sin_addr.s_addr == 0xffffffff)) {
                                uerr = EADDRNOTAVAIL;
                                goto nak;
                        }
                        gre->gre_faddr = sin->sin_addr.s_addr;
			gre->gre_ipha.ipha_dst = sin->sin_addr.s_addr;
                } else {
                        uerr = EAFNOSUPPORT;
                        goto nak;
                }
                gre->gre_flags |= TUN_DST;
	}
        mp->b_datap->db_type = M_IOCACK;
        iocp->ioc_error = 0;
	gre->gre_iocmp = mp;
	uerr = gre_send_bind_req(q, mp);
	if ( uerr == 0 ) return;
        qreply(q, mp);
        return;
nak:
        iocp->ioc_error = uerr;
        mp->b_datap->db_type = M_IOCNAK;
        qreply(q, mp);
}

int
c_gre_ioctl(queue_t *q, mblk_t *mp)
{
struct  iocblk  	*iocp = (struct iocblk *)(mp->b_rptr);
int			error, reterr = 0;
mblk_t			*mp1;
struct	iftun_req	*ta;
struct	sockaddr_in   	*sin;
struct	gre_s		*gre = q->q_ptr;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_ioctl()\n");
#endif
    switch (iocp->ioc_cmd) {
    case SIOCSTUNPARAM:
	qwriter(q, mp, c_gre_sparam, PERIM_INNER);
        return (0);
    case SIOCGTUNPARAM:
        mp1 = mp->b_cont;
        if (mp1 == NULL) {
                error = EPROTO;
                goto nak;
        }
        mp1 = mp1->b_cont;
        if (mp1 == NULL) {
                error = EPROTO;
                goto nak;
        }
        if (mp1->b_wptr - mp1->b_rptr != sizeof (struct iftun_req)) {
                error = EPROTO;
                goto nak;
        }
        ta = (struct iftun_req *)mp1->b_rptr;
        ta->ifta_flags = 0;
	ta->ifta_vers = IFTUN_VERSION;
	ta->ifta_upper = ta->ifta_lower = IFTAP_IPV4;

        sin = (struct sockaddr_in *)&ta->ifta_saddr;
        ta->ifta_lower = IFTAP_IPV4;
        bzero(sin, sizeof (struct sockaddr_in));
        sin->sin_family = AF_INET;
        if (gre->gre_flags & TUN_SRC) {
                sin->sin_addr.s_addr = gre->gre_laddr;
                ta->ifta_flags |= IFTUN_SRC;
        } else {
                sin->sin_addr.s_addr = 0;
        }

        sin = (struct sockaddr_in *)&ta->ifta_daddr;
        bzero(sin, sizeof (struct sockaddr_in));
        sin->sin_family = AF_INET;
        if (gre->gre_flags & TUN_DST) {
                sin->sin_addr.s_addr = gre->gre_faddr;
                ta->ifta_flags |= IFTUN_DST;
        } else {
                sin->sin_addr.s_addr = 0;
        }
	break;
    default:
	error = EINVAL;
	goto nak;
    }
    mp->b_datap->db_type = M_IOCACK;
    iocp->ioc_error = 0;
    qreply(q, mp);
    return (reterr);

nak:
    iocp->ioc_error = error;
    mp->b_datap->db_type = M_IOCNAK;
    qreply(q, mp);
    return (reterr);

}

int
c_gre_wputproc(queue_t *q, mblk_t *mp)
{
int	error = 0;
    switch(mp->b_datap->db_type) {
    case M_DATA:
	break;
    case M_IOCTL:
	error = c_gre_ioctl(q, mp);
	break;
    case M_PROTO:
    case M_PCPROTO:
	error = c_gre_wput_dlpi(q, mp);
	break;
    default:
	if ( canputnext(q) ) {
	    putnext(q, mp);
	} else {
	    putq(q, mp);
	}
	break;
    }
    return(error);
}

void
c_gre_wput(queue_t *q, mblk_t *mp)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_wput(): %d\n", mp->b_datap->db_type);
#endif
    (void)c_gre_wputproc(q, mp);
}

int
c_gre_rput_tpi(queue_t *q, mblk_t *mp)
{
struct gre_s    *gre = q->q_ptr;
t_uscalar_t prim = *((t_uscalar_t *)mp->b_rptr);
mblk_t		*iocmp;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_rput_tpi(%d)\n", prim);
#endif
   switch(prim) {
   case T_OK_ACK:
	freemsg(mp);
	break;
   case T_BIND_ACK: {
	ire_t   *ire;

        ASSERT((gre->gre_flags & TUN_BIND_SENT) != 0);
        ASSERT(gre->gre_iocmp != NULL);
        ire = (ire_t *)mp->b_cont->b_rptr;
	gre->gre_extra_offset = MAX(ire->ire_ll_hdr_length, 32);
	gre->gre_flags &= ~TUN_BIND_SENT;
	iocmp = gre->gre_iocmp;
        gre->gre_iocmp = NULL;
        freemsg(mp);
        putnext(q, iocmp);
        break;
	}
   default:
	break;
   }
   return(0);
}

static int
c_gre_rdata(queue_t *q, mblk_t *mp)
{
struct gre_s    *gre = q->q_ptr;
int             error = 0;
ipha_t		*iph, *inner_iph;
int		hdrlen;
mblk_t		*mp1;
gre_header_t	*gre_header;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_rdata()\n");
#endif
    ASSERT((mp->b_wptr - mp->b_rptr) >= sizeof (ipha_t));
    if (gre->gre_state != DL_IDLE) 
                goto drop;

    if (!canputnext(q)) {
        (void) putbq(q, mp);
        return (ENOMEM);
    }

    iph = (ipha_t *)mp->b_rptr;
    if (iph->ipha_protocol != IPPROTO_GRE) {
#ifdef	C_GRE_DEBUG
        cmn_err(CE_CONT, "c_gre_rdata(): not a GRE\n");
#endif
    }
    hdrlen = IPH_HDR_LENGTH(iph);

    ASSERT(IPH_HDR_VERSION(iph) == IPV4_VERSION);
    ASSERT(hdrlen >= sizeof (ipha_t));
    ASSERT(hdrlen <= (mp->b_wptr - mp->b_rptr));
    if ((mp->b_wptr - mp->b_rptr) == hdrlen) {
        mp1 = mp->b_cont;
        freeb(mp);
        mp = mp1;
        if (mp == NULL)
            return (0);
    } else {
        mp->b_rptr += hdrlen;
    }
    if ((mp->b_wptr - mp->b_rptr) < sizeof (ipha_t)) {
        if (!pullupmsg(mp, sizeof (ipha_t)) ) {
            goto drop;
        }
    }
    if ( mp == NULL ) {
	cmn_err(CE_CONT, "c_gre_rdata(): pullup failed");
	return(0);
    }
    gre_header = (gre_header_t*)mp->b_rptr;
#ifdef	C_GRE_DEBUG
        cmn_err(CE_CONT, "c_gre_rdata(): flag_ver:   %x\n", gre_header->flag_ver);
        cmn_err(CE_CONT, "c_gre_rdata(): proto_type: %x\n", gre_header->proto_type);
#endif
    
/*    mp->b_rptr += sizeof(gre_header_t);*/
    mp->b_rptr += 8;
    iph = (ipha_t*)mp->b_rptr;
#ifdef	C_GRE_DEBUG
        cmn_err(CE_CONT, "c_gre_rdata(): ipha->ver: %02x, proto: %02x\n",
		iph->ipha_version_and_hdr_length,
		iph->ipha_protocol);
#endif
   
    putnext(q, mp);
    return(0);
drop:
    freemsg(mp);
    return(error);
}

static int
c_gre_rputproc(queue_t *q, mblk_t *mp)
{
struct gre_s	*gre = q->q_ptr;
int		error = 0;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_rputproc()\n");
    cmn_err(CE_CONT, "c_gre_rputproc(%d)\n", mp->b_datap->db_type);
#endif
    switch (mp->b_datap->db_type) {
    case M_DATA:
	    error = c_gre_rdata(q, mp);
	    break;
    case M_PROTO:
    case M_PCPROTO:
            error = c_gre_rput_tpi(q, mp);
            break;
    default:
	    break;
    }
    return(error);
}

void
c_gre_rput(queue_t *q, mblk_t *mp)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_rput()\n");
#endif
    if ( canputnext(q) ) {
	(void)c_gre_rputproc(q, mp);
    } else {
	putq(q, mp);
    }
}

void
c_gre_rsrv(queue_t *q)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_rsrv()\n");
#endif
}

void
c_gre_wsrv(queue_t *q)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_wsrv()\n");
#endif
}

int
c_gre_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
{
struct	gre_s	*gre;
ipha_t		*ipha;

#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_open()\n");
#endif
    if ( q->q_ptr ) return(0);

    if (sflag != MODOPEN) {
        return (EINVAL);
    }

    gre = kmem_zalloc(sizeof (struct gre_s), KM_SLEEP);
    gre->gre_state = DL_UNATTACHED;
    gre->gre_mtu   = 4096 ;
    gre->gre_extra_offset = 32;
    ipha = &gre->gre_ipha;
    ipha->ipha_version_and_hdr_length = IP_SIMPLE_HDR_VERSION;
    ipha->ipha_protocol = IPPROTO_GRE;
    ipha->ipha_type_of_service = 0;
    ipha->ipha_ident = 0;
    ipha->ipha_fragment_offset_and_flags = 0 /*htons(IPH_DF)*/;
    ipha->ipha_ttl = 255;
    ipha->ipha_hdr_checksum = 0;

    q->q_ptr = WR(q)->q_ptr = gre;
    qprocson(q);
    return(0);
}

int
c_gre_close(queue_t *q, int flag, cred_t *credp)
{
#ifdef	C_GRE_DEBUG
    cmn_err(CE_CONT, "c_gre_close()\n");
#endif
    qprocsoff(q);
    kmem_free(q->q_ptr, sizeof(struct gre_s));
    q->q_ptr = WR(q)->q_ptr = NULL;
    return(0);
}

static mblk_t *
gre_realloc_mblk(queue_t *q, mblk_t *mp, size_t size, mblk_t *orig_mp,
    boolean_t copy)
{
	if (mp == NULL || mp->b_datap->db_lim - mp->b_datap->db_base < size ||
	    mp->b_datap->db_ref > 1) {
		size_t	asize;
		mblk_t *newmp;

		if (mp != NULL) {
			asize = MAX(size,
			    mp->b_datap->db_lim - mp->b_datap->db_base);
		} else {
			asize = size;
		}
		newmp = allocb(asize, BPRI_HI);

		if (newmp == NULL) {
			return (NULL);
		}
		if (mp != NULL) {
			if (copy)
				bcopy(mp->b_rptr, newmp->b_rptr,
				    mp->b_wptr - mp->b_rptr);
			newmp->b_datap->db_type = mp->b_datap->db_type;
			freemsg(mp);
		}
		mp = newmp;
	} else {
		if (mp->b_rptr != mp->b_datap->db_base) {
			if (copy)
				bcopy(mp->b_rptr, mp->b_datap->db_base,
				    mp->b_wptr - mp->b_rptr);
			mp->b_rptr = mp->b_datap->db_base;
		}
	}
	mp->b_wptr = mp->b_rptr + size;
	return (mp);
}

