#include <sys/types.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/stropts.h>
#include <sys/time.h>
#include <sys/stream.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/strsun.h>
#include <sys/bufmod.h>
#include <sys/modctl.h>
#include <sys/isa_defs.h>
struct sb {
queue_t *sb_rq;
mblk_t *sb_mp;
mblk_t *sb_head;
mblk_t *sb_tail;
uint_t sb_mlen;
uint_t sb_mcount;
uint_t sb_chunk;
clock_t sb_ticks;
timeout_id_t sb_timeoutid;
uint_t sb_drops;
uint_t sb_snap;
uint_t sb_flags;
uint_t sb_state;
};
static int sbopen(queue_t *, dev_t *, int, int, cred_t *);
static int sbclose(queue_t *, int, cred_t *);
static int sbwput(queue_t *, mblk_t *);
static int sbrput(queue_t *, mblk_t *);
static int sbrsrv(queue_t *);
static void sbioctl(queue_t *, mblk_t *);
static void sbaddmsg(queue_t *, mblk_t *);
static void sbtick(void *);
static void sbclosechunk(struct sb *);
static void sbsendit(queue_t *, mblk_t *);
static struct module_info sb_minfo = {
21,
"bufmod",
0,
INFPSZ,
1,
0
};
static struct qinit sb_rinit = {
sbrput,
sbrsrv,
sbopen,
sbclose,
NULL,
&sb_minfo,
NULL
};
static struct qinit sb_winit = {
sbwput,
NULL,
NULL,
NULL,
NULL,
&sb_minfo,
NULL
};
static struct streamtab sb_info = {
&sb_rinit,
&sb_winit,
NULL,
NULL
};
static struct fmodsw fsw = {
"bufmod",
&sb_info,
D_MTQPAIR | D_MP
};
static struct modlstrmod modlstrmod = {
&mod_strmodops, "streams buffer mod", &fsw
};
static struct modlinkage modlinkage = {
MODREV_1, &modlstrmod, NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
sbopen(queue_t *rq, dev_t *dev, int oflag, int sflag, cred_t *crp)
{
struct sb *sbp;
ASSERT(rq);
if (sflag != MODOPEN)
return (EINVAL);
if (rq->q_ptr)
return (0);
sbp = kmem_alloc(sizeof (struct sb), KM_SLEEP);
sbp->sb_rq = rq;
sbp->sb_ticks = -1;
sbp->sb_chunk = SB_DFLT_CHUNK;
sbp->sb_tail = sbp->sb_mp = sbp->sb_head = NULL;
sbp->sb_mlen = 0;
sbp->sb_mcount = 0;
sbp->sb_timeoutid = 0;
sbp->sb_drops = 0;
sbp->sb_snap = 0;
sbp->sb_flags = 0;
sbp->sb_state = 0;
rq->q_ptr = WR(rq)->q_ptr = sbp;
qprocson(rq);
return (0);
}
static int
sbclose(queue_t *rq, int flag, cred_t *credp)
{
struct sb *sbp = (struct sb *)rq->q_ptr;
ASSERT(sbp);
qprocsoff(rq);
if (sbp->sb_timeoutid != 0) {
(void) quntimeout(rq, sbp->sb_timeoutid);
sbp->sb_timeoutid = 0;
}
if (sbp->sb_mp) {
freemsg(sbp->sb_mp);
sbp->sb_tail = sbp->sb_mp = sbp->sb_head = NULL;
sbp->sb_mlen = 0;
}
kmem_free((caddr_t)sbp, sizeof (struct sb));
rq->q_ptr = WR(rq)->q_ptr = NULL;
return (0);
}
#define SNIT_HIWAT(msgsize, fudge) ((4 * msgsize * fudge) + 512)
#define SNIT_LOWAT(msgsize, fudge) ((2 * msgsize * fudge) + 256)
static void
sbioc(queue_t *wq, mblk_t *mp)
{
struct iocblk *iocp;
struct sb *sbp = (struct sb *)wq->q_ptr;
clock_t ticks;
mblk_t *mop;
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
case SBIOCGCHUNK:
case SBIOCGSNAP:
case SBIOCGFLAGS:
case SBIOCGTIME:
miocack(wq, mp, 0, 0);
return;
case SBIOCSTIME:
#ifdef _SYSCALL32_IMPL
if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) {
struct timeval32 *t32;
t32 = (struct timeval32 *)mp->b_cont->b_rptr;
if (t32->tv_sec < 0 || t32->tv_usec < 0) {
miocnak(wq, mp, 0, EINVAL);
break;
}
ticks = TIMEVAL_TO_TICK(t32);
} else
#endif
{
struct timeval *tb;
tb = (struct timeval *)mp->b_cont->b_rptr;
if (tb->tv_sec < 0 || tb->tv_usec < 0) {
miocnak(wq, mp, 0, EINVAL);
break;
}
ticks = TIMEVAL_TO_TICK(tb);
}
sbp->sb_ticks = ticks;
if (ticks == 0)
sbp->sb_chunk = 0;
miocack(wq, mp, 0, 0);
sbclosechunk(sbp);
return;
case SBIOCSCHUNK:
if ((mop = allocb(sizeof (struct stroptions),
BPRI_MED)) != NULL) {
struct stroptions *sop;
uint_t chunk;
chunk = *(uint_t *)mp->b_cont->b_rptr;
mop->b_datap->db_type = M_SETOPTS;
mop->b_wptr += sizeof (struct stroptions);
sop = (struct stroptions *)mop->b_rptr;
sop->so_flags = SO_HIWAT | SO_LOWAT;
sop->so_hiwat = SNIT_HIWAT(chunk, 1);
sop->so_lowat = SNIT_LOWAT(chunk, 1);
qreply(wq, mop);
}
sbp->sb_chunk = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
sbclosechunk(sbp);
return;
case SBIOCSFLAGS:
sbp->sb_flags = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
return;
case SBIOCSSNAP:
if (!sbp->sb_chunk) {
if ((mop = allocb(sizeof (struct stroptions),
BPRI_MED)) != NULL) {
struct stroptions *sop;
uint_t snap;
int fudge;
snap = *(uint_t *)mp->b_cont->b_rptr;
mop->b_datap->db_type = M_SETOPTS;
mop->b_wptr += sizeof (struct stroptions);
sop = (struct stroptions *)mop->b_rptr;
sop->so_flags = SO_HIWAT | SO_LOWAT;
fudge = snap <= 100 ? 4 :
snap <= 400 ? 2 :
1;
sop->so_hiwat = SNIT_HIWAT(snap, fudge);
sop->so_lowat = SNIT_LOWAT(snap, fudge);
qreply(wq, mop);
}
}
sbp->sb_snap = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
return;
default:
ASSERT(0);
return;
}
}
static int
sbwput(queue_t *wq, mblk_t *mp)
{
struct sb *sbp = (struct sb *)wq->q_ptr;
struct copyresp *resp;
if (sbp->sb_flags & SB_SEND_ON_WRITE)
sbclosechunk(sbp);
switch (mp->b_datap->db_type) {
case M_IOCTL:
sbioctl(wq, mp);
break;
case M_IOCDATA:
resp = (struct copyresp *)mp->b_rptr;
if (resp->cp_rval) {
freemsg(mp);
break;
}
switch (resp->cp_cmd) {
case SBIOCSTIME:
case SBIOCSCHUNK:
case SBIOCSFLAGS:
case SBIOCSSNAP:
case SBIOCGTIME:
case SBIOCGCHUNK:
case SBIOCGSNAP:
case SBIOCGFLAGS:
sbioc(wq, mp);
break;
default:
putnext(wq, mp);
break;
}
break;
default:
putnext(wq, mp);
break;
}
return (0);
}
static int
sbrput(queue_t *rq, mblk_t *mp)
{
struct sb *sbp = (struct sb *)rq->q_ptr;
ASSERT(sbp);
switch (mp->b_datap->db_type) {
case M_PROTO:
if (sbp->sb_flags & SB_NO_PROTO_CVT) {
sbclosechunk(sbp);
sbsendit(rq, mp);
break;
} else {
mp->b_datap->db_type = M_DATA;
}
case M_DATA:
if ((sbp->sb_flags & SB_DEFER_CHUNK) &&
!(sbp->sb_state & SB_FRCVD)) {
sbclosechunk(sbp);
sbsendit(rq, mp);
sbp->sb_state |= SB_FRCVD;
} else
sbaddmsg(rq, mp);
if ((sbp->sb_ticks > 0) && !(sbp->sb_timeoutid))
sbp->sb_timeoutid = qtimeout(sbp->sb_rq, sbtick,
sbp, sbp->sb_ticks);
break;
case M_FLUSH:
if (*mp->b_rptr & FLUSHR) {
if (sbp->sb_timeoutid) {
(void) quntimeout(sbp->sb_rq,
sbp->sb_timeoutid);
sbp->sb_timeoutid = 0;
}
if (sbp->sb_mp) {
freemsg(sbp->sb_mp);
sbp->sb_tail = sbp->sb_mp = sbp->sb_head = NULL;
sbp->sb_mlen = 0;
sbp->sb_mcount = 0;
}
flushq(rq, FLUSHALL);
}
putnext(rq, mp);
break;
case M_CTL:
if (MBLKL(mp) == 0) {
freemsg(mp);
sbclosechunk(sbp);
} else {
sbclosechunk(sbp);
sbsendit(rq, mp);
}
break;
default:
if (mp->b_datap->db_type <= QPCTL) {
sbclosechunk(sbp);
sbsendit(rq, mp);
} else {
putnext(rq, mp);
}
break;
}
return (0);
}
static int
sbrsrv(queue_t *rq)
{
mblk_t *mp;
while ((mp = getq(rq)) != NULL) {
if (!canputnext(rq) && (mp->b_datap->db_type <= QPCTL)) {
(void) putbq(rq, mp);
return (0);
}
putnext(rq, mp);
}
return (0);
}
static void
sbioctl(queue_t *wq, mblk_t *mp)
{
struct sb *sbp = (struct sb *)wq->q_ptr;
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
struct timeval *t;
clock_t ticks;
mblk_t *mop;
int transparent = iocp->ioc_count;
mblk_t *datamp;
int error;
switch (iocp->ioc_cmd) {
case SBIOCSTIME:
if (iocp->ioc_count == TRANSPARENT) {
#ifdef _SYSCALL32_IMPL
if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) {
mcopyin(mp, NULL, sizeof (struct timeval32),
NULL);
} else
#endif
{
mcopyin(mp, NULL, sizeof (*t), NULL);
}
qreply(wq, mp);
} else {
#ifdef _SYSCALL32_IMPL
if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) {
struct timeval32 *t32;
error = miocpullup(mp,
sizeof (struct timeval32));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
t32 = (struct timeval32 *)mp->b_cont->b_rptr;
if (t32->tv_sec < 0 || t32->tv_usec < 0) {
miocnak(wq, mp, 0, EINVAL);
break;
}
ticks = TIMEVAL_TO_TICK(t32);
} else
#endif
{
error = miocpullup(mp, sizeof (struct timeval));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
t = (struct timeval *)mp->b_cont->b_rptr;
if (t->tv_sec < 0 || t->tv_usec < 0) {
miocnak(wq, mp, 0, EINVAL);
break;
}
ticks = TIMEVAL_TO_TICK(t);
}
sbp->sb_ticks = ticks;
if (ticks == 0)
sbp->sb_chunk = 0;
miocack(wq, mp, 0, 0);
sbclosechunk(sbp);
}
break;
case SBIOCGTIME: {
struct timeval *t;
if (transparent != TRANSPARENT) {
#ifdef _SYSCALL32_IMPL
if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) {
error = miocpullup(mp,
sizeof (struct timeval32));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
} else
#endif
error = miocpullup(mp, sizeof (struct timeval));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
}
if (sbp->sb_ticks < 0) {
miocnak(wq, mp, 0, ERANGE);
break;
}
#ifdef _SYSCALL32_IMPL
if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) {
struct timeval32 *t32;
if (transparent == TRANSPARENT) {
datamp = allocb(sizeof (*t32), BPRI_MED);
if (datamp == NULL) {
miocnak(wq, mp, 0, EAGAIN);
break;
}
mcopyout(mp, NULL, sizeof (*t32), NULL, datamp);
}
t32 = (struct timeval32 *)mp->b_cont->b_rptr;
TICK_TO_TIMEVAL32(sbp->sb_ticks, t32);
if (transparent == TRANSPARENT)
qreply(wq, mp);
else
miocack(wq, mp, sizeof (*t32), 0);
} else
#endif
{
if (transparent == TRANSPARENT) {
datamp = allocb(sizeof (*t), BPRI_MED);
if (datamp == NULL) {
miocnak(wq, mp, 0, EAGAIN);
break;
}
mcopyout(mp, NULL, sizeof (*t), NULL, datamp);
}
t = (struct timeval *)mp->b_cont->b_rptr;
TICK_TO_TIMEVAL(sbp->sb_ticks, t);
if (transparent == TRANSPARENT)
qreply(wq, mp);
else
miocack(wq, mp, sizeof (*t), 0);
}
break;
}
case SBIOCCTIME:
sbp->sb_ticks = -1;
miocack(wq, mp, 0, 0);
break;
case SBIOCSCHUNK:
if (iocp->ioc_count == TRANSPARENT) {
mcopyin(mp, NULL, sizeof (uint_t), NULL);
qreply(wq, mp);
} else {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
if ((mop = allocb(sizeof (struct stroptions),
BPRI_MED)) != NULL) {
struct stroptions *sop;
uint_t chunk;
chunk = *(uint_t *)mp->b_cont->b_rptr;
mop->b_datap->db_type = M_SETOPTS;
mop->b_wptr += sizeof (struct stroptions);
sop = (struct stroptions *)mop->b_rptr;
sop->so_flags = SO_HIWAT | SO_LOWAT;
sop->so_hiwat = SNIT_HIWAT(chunk, 1);
sop->so_lowat = SNIT_LOWAT(chunk, 1);
qreply(wq, mop);
}
sbp->sb_chunk = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
sbclosechunk(sbp);
}
break;
case SBIOCGCHUNK:
if (transparent != TRANSPARENT) {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
}
if (transparent == TRANSPARENT) {
datamp = allocb(sizeof (uint_t), BPRI_MED);
if (datamp == NULL) {
miocnak(wq, mp, 0, EAGAIN);
break;
}
mcopyout(mp, NULL, sizeof (uint_t), NULL, datamp);
}
*(uint_t *)mp->b_cont->b_rptr = sbp->sb_chunk;
if (transparent == TRANSPARENT)
qreply(wq, mp);
else
miocack(wq, mp, sizeof (uint_t), 0);
break;
case SBIOCSSNAP:
if (iocp->ioc_count == TRANSPARENT) {
mcopyin(mp, NULL, sizeof (uint_t), NULL);
qreply(wq, mp);
} else {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
if (!sbp->sb_chunk) {
if ((mop = allocb(sizeof (struct stroptions),
BPRI_MED)) != NULL) {
struct stroptions *sop;
uint_t snap;
int fudge;
snap = *(uint_t *)mp->b_cont->b_rptr;
mop->b_datap->db_type = M_SETOPTS;
mop->b_wptr += sizeof (*sop);
sop = (struct stroptions *)mop->b_rptr;
sop->so_flags = SO_HIWAT | SO_LOWAT;
fudge = (snap <= 100) ? 4 :
(snap <= 400) ? 2 : 1;
sop->so_hiwat = SNIT_HIWAT(snap, fudge);
sop->so_lowat = SNIT_LOWAT(snap, fudge);
qreply(wq, mop);
}
}
sbp->sb_snap = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
}
break;
case SBIOCGSNAP:
if (transparent != TRANSPARENT) {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
}
if (transparent == TRANSPARENT) {
datamp = allocb(sizeof (uint_t), BPRI_MED);
if (datamp == NULL) {
miocnak(wq, mp, 0, EAGAIN);
break;
}
mcopyout(mp, NULL, sizeof (uint_t), NULL, datamp);
}
*(uint_t *)mp->b_cont->b_rptr = sbp->sb_snap;
if (transparent == TRANSPARENT)
qreply(wq, mp);
else
miocack(wq, mp, sizeof (uint_t), 0);
break;
case SBIOCSFLAGS:
if (iocp->ioc_count == TRANSPARENT) {
mcopyin(mp, NULL, sizeof (uint_t), NULL);
qreply(wq, mp);
} else {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
sbp->sb_flags = *(uint_t *)mp->b_cont->b_rptr;
miocack(wq, mp, 0, 0);
}
break;
case SBIOCGFLAGS:
if (transparent != TRANSPARENT) {
error = miocpullup(mp, sizeof (uint_t));
if (error != 0) {
miocnak(wq, mp, 0, error);
break;
}
}
if (transparent == TRANSPARENT) {
datamp = allocb(sizeof (uint_t), BPRI_MED);
if (datamp == NULL) {
miocnak(wq, mp, 0, EAGAIN);
break;
}
mcopyout(mp, NULL, sizeof (uint_t), NULL, datamp);
}
*(uint_t *)mp->b_cont->b_rptr = sbp->sb_flags;
if (transparent == TRANSPARENT)
qreply(wq, mp);
else
miocack(wq, mp, sizeof (uint_t), 0);
break;
default:
putnext(wq, mp);
break;
}
}
#define RoundUpAmt(l, a) ((l) % (a) ? (a) - ((l) % (a)) : 0)
#define Align(l) RoundUpAmt(l, sizeof (ulong_t))
#define SMALLEST_MESSAGE sizeof (struct sb_hdr) + _POINTER_ALIGNMENT
static void
sbaddmsg(queue_t *rq, mblk_t *mp)
{
struct sb *sbp;
struct timeval t;
struct sb_hdr hp;
mblk_t *wrapper;
mblk_t *last;
size_t wrapperlen;
size_t origlen;
size_t pad;
sbp = (struct sb *)rq->q_ptr;
origlen = msgdsize(mp);
if ((sbp->sb_snap > 0) && (origlen > sbp->sb_snap) &&
(adjmsg(mp, -(origlen - sbp->sb_snap)) == 1))
hp.sbh_totlen = hp.sbh_msglen = sbp->sb_snap;
else
hp.sbh_totlen = hp.sbh_msglen = origlen;
if (sbp->sb_flags & SB_NO_HEADER) {
if ((hp.sbh_totlen + sbp->sb_mlen) > sbp->sb_chunk)
sbclosechunk(sbp);
if (hp.sbh_totlen > sbp->sb_chunk) {
sbsendit(rq, mp);
return;
}
if (sbp->sb_mp)
linkb(sbp->sb_tail, mp);
else
sbp->sb_mp = mp;
sbp->sb_tail = mp;
sbp->sb_mlen += hp.sbh_totlen;
sbp->sb_mcount++;
} else {
uniqtime(&t);
TIMEVAL_TO_TIMEVAL32(&hp.sbh_timestamp, &t);
pad = Align(hp.sbh_totlen);
hp.sbh_totlen += sizeof (hp);
if ((sbp->sb_mlen + hp.sbh_totlen) > sbp->sb_chunk)
sbclosechunk(sbp);
if (sbp->sb_head == NULL) {
sbp->sb_head = allocb(sizeof (hp), BPRI_MED);
if (sbp->sb_head == NULL) {
freemsg(mp);
sbp->sb_drops++;
return;
}
sbp->sb_mp = sbp->sb_head;
}
hp.sbh_drops = sbp->sb_drops;
hp.sbh_origlen = origlen;
linkb(sbp->sb_head, mp);
sbp->sb_mcount++;
sbp->sb_mlen += hp.sbh_totlen;
if ((sbp->sb_mlen + pad + SMALLEST_MESSAGE) > sbp->sb_chunk) {
(void) memcpy(sbp->sb_head->b_wptr, (char *)&hp,
sizeof (hp));
sbp->sb_head->b_wptr += sizeof (hp);
ASSERT(sbp->sb_head->b_wptr <=
sbp->sb_head->b_datap->db_lim);
sbclosechunk(sbp);
return;
}
hp.sbh_totlen += pad;
(void) memcpy(sbp->sb_head->b_wptr, (char *)&hp, sizeof (hp));
sbp->sb_head->b_wptr += sizeof (hp);
ASSERT(sbp->sb_head->b_wptr <= sbp->sb_head->b_datap->db_lim);
sbp->sb_mlen += pad;
wrapperlen = (sizeof (hp) + pad);
for (last = mp; last->b_cont; last = last->b_cont)
;
if ((wrapperlen <= MBLKTAIL(last)) &&
(last->b_datap->db_ref == 1)) {
if (pad > 0) {
(void) memset(last->b_wptr, 0, pad);
last->b_wptr += pad;
}
sbp->sb_head = last;
} else {
wrapper = allocb(wrapperlen, BPRI_MED);
if (wrapper == NULL) {
sbclosechunk(sbp);
return;
}
if (pad > 0) {
(void) memset(wrapper->b_wptr, 0, pad);
wrapper->b_wptr += pad;
}
linkb(mp, wrapper);
sbp->sb_head = wrapper;
}
}
}
static void
sbtick(void *arg)
{
struct sb *sbp = arg;
queue_t *rq;
ASSERT(sbp);
rq = sbp->sb_rq;
sbp->sb_timeoutid = 0;
if (putctl(rq, M_CTL) == 0)
sbp->sb_timeoutid = qtimeout(rq, sbtick, sbp, sbp->sb_ticks);
}
static void
sbclosechunk(struct sb *sbp)
{
mblk_t *mp;
queue_t *rq;
ASSERT(sbp);
if (sbp->sb_timeoutid) {
(void) quntimeout(sbp->sb_rq, sbp->sb_timeoutid);
sbp->sb_timeoutid = 0;
}
mp = sbp->sb_mp;
rq = sbp->sb_rq;
if (mp) {
sbsendit(rq, mp);
}
sbp->sb_tail = sbp->sb_mp = sbp->sb_head = NULL;
sbp->sb_mlen = 0;
sbp->sb_mcount = 0;
if (sbp->sb_flags & SB_DEFER_CHUNK)
sbp->sb_state &= ~SB_FRCVD;
}
static void
sbsendit(queue_t *rq, mblk_t *mp)
{
struct sb *sbp = (struct sb *)rq->q_ptr;
if (!canputnext(rq)) {
if (sbp->sb_flags & SB_NO_DROPS)
(void) putq(rq, mp);
else {
freemsg(mp);
sbp->sb_drops += sbp->sb_mcount;
}
return;
}
if (qsize(rq) > 0) {
(void) putq(rq, mp);
}
else
putnext(rq, mp);
}