NetBSD/sys/dev/kttcp.c

726 lines
19 KiB
C

/* $NetBSD: kttcp.c,v 1.18 2006/05/18 18:45:48 mrg Exp $ */
/*
* Copyright (c) 2002 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Frank van der Linden and Jason R. Thorpe for
* Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* kttcp.c --
*
* This module provides kernel support for testing network
* throughput from the perspective of the kernel. It is
* similar in spirit to the classic ttcp network benchmark
* program, the main difference being that with kttcp, the
* kernel is the source and sink of the data.
*
* Testing like this is useful for a few reasons:
*
* 1. This allows us to know what kind of performance we can
* expect from network applications that run in the kernel
* space, such as the NFS server or the NFS client. These
* applications don't have to move the data to/from userspace,
* and so benchmark programs which run in userspace don't
* give us an accurate model.
*
* 2. Since data received is just thrown away, the receiver
* is very fast. This can provide better exercise for the
* sender at the other end.
*
* 3. Since the NetBSD kernel currently uses a run-to-completion
* scheduling model, kttcp provides a benchmark model where
* preemption of the benchmark program is not an issue.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kttcp.c,v 1.18 2006/05/18 18:45:48 mrg Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/conf.h>
#include <sys/systm.h>
#include <sys/protosw.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/signal.h>
#include <sys/socketvar.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/sa.h>
#include <sys/mount.h>
#include <sys/syscallargs.h>
#include <dev/kttcpio.h>
static int kttcp_send(struct lwp *l, struct kttcp_io_args *);
static int kttcp_recv(struct lwp *l, struct kttcp_io_args *);
static int kttcp_sosend(struct socket *, unsigned long long,
unsigned long long *, struct lwp *, int);
static int kttcp_soreceive(struct socket *, unsigned long long,
unsigned long long *, struct lwp *, int *);
void kttcpattach(int);
dev_type_ioctl(kttcpioctl);
const struct cdevsw kttcp_cdevsw = {
nullopen, nullclose, noread, nowrite, kttcpioctl,
nostop, notty, nopoll, nommap, nokqfilter,
};
void
kttcpattach(int count)
{
/* Do nothing. */
}
int
kttcpioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct lwp *l)
{
int error;
if ((flag & FWRITE) == 0)
return EPERM;
switch (cmd) {
case KTTCP_IO_SEND:
error = kttcp_send(l, (struct kttcp_io_args *) data);
break;
case KTTCP_IO_RECV:
error = kttcp_recv(l, (struct kttcp_io_args *) data);
break;
default:
return EINVAL;
}
return error;
}
static int
kttcp_send(struct lwp *l, struct kttcp_io_args *kio)
{
struct file *fp;
int error;
struct timeval t0, t1;
unsigned long long len, done;
if (kio->kio_totalsize >= KTTCP_MAX_XMIT)
return EINVAL;
fp = fd_getfile(l->l_proc->p_fd, kio->kio_socket);
if (fp == NULL)
return EBADF;
FILE_USE(fp);
if (fp->f_type != DTYPE_SOCKET) {
FILE_UNUSE(fp, l);
return EFTYPE;
}
len = kio->kio_totalsize;
microtime(&t0);
do {
error = kttcp_sosend((struct socket *)fp->f_data, len,
&done, l, 0);
len -= done;
} while (error == 0 && len > 0);
FILE_UNUSE(fp, l);
microtime(&t1);
if (error != 0)
return error;
timersub(&t1, &t0, &kio->kio_elapsed);
kio->kio_bytesdone = kio->kio_totalsize - len;
return 0;
}
static int
kttcp_recv(struct lwp *l, struct kttcp_io_args *kio)
{
struct file *fp;
int error;
struct timeval t0, t1;
unsigned long long len, done;
done = 0; /* XXX gcc */
if (kio->kio_totalsize > KTTCP_MAX_XMIT)
return EINVAL;
fp = fd_getfile(l->l_proc->p_fd, kio->kio_socket);
if (fp == NULL)
return EBADF;
FILE_USE(fp);
if (fp->f_type != DTYPE_SOCKET) {
FILE_UNUSE(fp, l);
return EBADF;
}
len = kio->kio_totalsize;
microtime(&t0);
do {
error = kttcp_soreceive((struct socket *)fp->f_data,
len, &done, l, NULL);
len -= done;
} while (error == 0 && len > 0 && done > 0);
FILE_UNUSE(fp, l);
microtime(&t1);
if (error == EPIPE)
error = 0;
if (error != 0)
return error;
timersub(&t1, &t0, &kio->kio_elapsed);
kio->kio_bytesdone = kio->kio_totalsize - len;
return 0;
}
#define SBLOCKWAIT(f) (((f) & MSG_DONTWAIT) ? M_NOWAIT : M_WAITOK)
/*
* Slightly changed version of sosend()
*/
static int
kttcp_sosend(struct socket *so, unsigned long long slen,
unsigned long long *done, struct lwp *l, int flags)
{
struct mbuf **mp, *m, *top;
long space, len, mlen;
int error, s, dontroute, atomic;
long long resid;
atomic = sosendallatonce(so);
resid = slen;
top = NULL;
/*
* In theory resid should be unsigned.
* However, space must be signed, as it might be less than 0
* if we over-committed, and we must use a signed comparison
* of space and resid. On the other hand, a negative resid
* causes us to loop sending 0-length segments to the protocol.
*/
if (resid < 0) {
error = EINVAL;
goto out;
}
dontroute =
(flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 &&
(so->so_proto->pr_flags & PR_ATOMIC);
/* WRS XXX - are we doing per-lwp or per-proc stats? */
l->l_proc->p_stats->p_ru.ru_msgsnd++;
#define snderr(errno) { error = errno; splx(s); goto release; }
restart:
if ((error = sblock(&so->so_snd, SBLOCKWAIT(flags))) != 0)
goto out;
do {
s = splsoftnet();
if (so->so_state & SS_CANTSENDMORE)
snderr(EPIPE);
if (so->so_error) {
error = so->so_error;
so->so_error = 0;
splx(s);
goto release;
}
if ((so->so_state & SS_ISCONNECTED) == 0) {
if (so->so_proto->pr_flags & PR_CONNREQUIRED) {
if ((so->so_state & SS_ISCONFIRMING) == 0)
snderr(ENOTCONN);
} else
snderr(EDESTADDRREQ);
}
space = sbspace(&so->so_snd);
if (flags & MSG_OOB)
space += 1024;
if ((atomic && resid > so->so_snd.sb_hiwat))
snderr(EMSGSIZE);
if (space < resid && (atomic || space < so->so_snd.sb_lowat)) {
if (so->so_state & SS_NBIO)
snderr(EWOULDBLOCK);
SBLASTRECORDCHK(&so->so_rcv,
"kttcp_soreceive sbwait 1");
SBLASTMBUFCHK(&so->so_rcv,
"kttcp_soreceive sbwait 1");
sbunlock(&so->so_snd);
error = sbwait(&so->so_snd);
splx(s);
if (error)
goto out;
goto restart;
}
splx(s);
mp = &top;
do {
do {
if (top == 0) {
m = m_gethdr(M_WAIT, MT_DATA);
mlen = MHLEN;
m->m_pkthdr.len = 0;
m->m_pkthdr.rcvif = NULL;
} else {
m = m_get(M_WAIT, MT_DATA);
mlen = MLEN;
}
if (resid >= MINCLSIZE && space >= MCLBYTES) {
m_clget(m, M_WAIT);
if ((m->m_flags & M_EXT) == 0)
goto nopages;
mlen = MCLBYTES;
#ifdef MAPPED_MBUFS
len = lmin(MCLBYTES, resid);
#else
if (atomic && top == 0) {
len = lmin(MCLBYTES - max_hdr,
resid);
m->m_data += max_hdr;
} else
len = lmin(MCLBYTES, resid);
#endif
space -= len;
} else {
nopages:
len = lmin(lmin(mlen, resid), space);
space -= len;
/*
* For datagram protocols, leave room
* for protocol headers in first mbuf.
*/
if (atomic && top == 0 && len < mlen)
MH_ALIGN(m, len);
}
resid -= len;
m->m_len = len;
*mp = m;
top->m_pkthdr.len += len;
if (error)
goto release;
mp = &m->m_next;
if (resid <= 0) {
if (flags & MSG_EOR)
top->m_flags |= M_EOR;
break;
}
} while (space > 0 && atomic);
s = splsoftnet();
if (so->so_state & SS_CANTSENDMORE)
snderr(EPIPE);
if (dontroute)
so->so_options |= SO_DONTROUTE;
if (resid > 0)
so->so_state |= SS_MORETOCOME;
error = (*so->so_proto->pr_usrreq)(so,
(flags & MSG_OOB) ? PRU_SENDOOB : PRU_SEND,
top, NULL, NULL, l);
if (dontroute)
so->so_options &= ~SO_DONTROUTE;
if (resid > 0)
so->so_state &= ~SS_MORETOCOME;
splx(s);
top = 0;
mp = &top;
if (error)
goto release;
} while (resid && space > 0);
} while (resid);
release:
sbunlock(&so->so_snd);
out:
if (top)
m_freem(top);
*done = slen - resid;
#if 0
printf("sosend: error %d slen %llu resid %lld\n", error, slen, resid);
#endif
return (error);
}
static int
kttcp_soreceive(struct socket *so, unsigned long long slen,
unsigned long long *done, struct lwp *l, int *flagsp)
{
struct mbuf *m, **mp;
int flags, len, error, s, offset, moff, type;
long long orig_resid, resid;
const struct protosw *pr;
struct mbuf *nextrecord;
pr = so->so_proto;
mp = NULL;
type = 0;
resid = orig_resid = slen;
if (flagsp)
flags = *flagsp &~ MSG_EOR;
else
flags = 0;
if (flags & MSG_OOB) {
m = m_get(M_WAIT, MT_DATA);
error = (*pr->pr_usrreq)(so, PRU_RCVOOB, m,
(struct mbuf *)(long)(flags & MSG_PEEK), NULL, NULL);
if (error)
goto bad;
do {
resid -= min(resid, m->m_len);
m = m_free(m);
} while (resid && error == 0 && m);
bad:
if (m)
m_freem(m);
return (error);
}
if (mp)
*mp = NULL;
if (so->so_state & SS_ISCONFIRMING && resid)
(*pr->pr_usrreq)(so, PRU_RCVD, NULL, NULL, NULL, NULL);
restart:
if ((error = sblock(&so->so_rcv, SBLOCKWAIT(flags))) != 0)
return (error);
s = splsoftnet();
m = so->so_rcv.sb_mb;
/*
* If we have less data than requested, block awaiting more
* (subject to any timeout) if:
* 1. the current count is less than the low water mark,
* 2. MSG_WAITALL is set, and it is possible to do the entire
* receive operation at once if we block (resid <= hiwat), or
* 3. MSG_DONTWAIT is not set.
* If MSG_WAITALL is set but resid is larger than the receive buffer,
* we have to do the receive in sections, and thus risk returning
* a short count if a timeout or signal occurs after we start.
*/
if (m == NULL || (((flags & MSG_DONTWAIT) == 0 &&
so->so_rcv.sb_cc < resid) &&
(so->so_rcv.sb_cc < so->so_rcv.sb_lowat ||
((flags & MSG_WAITALL) && resid <= so->so_rcv.sb_hiwat)) &&
m->m_nextpkt == NULL && (pr->pr_flags & PR_ATOMIC) == 0)) {
#ifdef DIAGNOSTIC
if (m == NULL && so->so_rcv.sb_cc)
panic("receive 1");
#endif
if (so->so_error) {
if (m)
goto dontblock;
error = so->so_error;
if ((flags & MSG_PEEK) == 0)
so->so_error = 0;
goto release;
}
if (so->so_state & SS_CANTRCVMORE) {
if (m)
goto dontblock;
else
goto release;
}
for (; m; m = m->m_next)
if (m->m_type == MT_OOBDATA || (m->m_flags & M_EOR)) {
m = so->so_rcv.sb_mb;
goto dontblock;
}
if ((so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0 &&
(so->so_proto->pr_flags & PR_CONNREQUIRED)) {
error = ENOTCONN;
goto release;
}
if (resid == 0)
goto release;
if ((so->so_state & SS_NBIO) || (flags & MSG_DONTWAIT)) {
error = EWOULDBLOCK;
goto release;
}
sbunlock(&so->so_rcv);
error = sbwait(&so->so_rcv);
splx(s);
if (error)
return (error);
goto restart;
}
dontblock:
/*
* On entry here, m points to the first record of the socket buffer.
* While we process the initial mbufs containing address and control
* info, we save a copy of m->m_nextpkt into nextrecord.
*/
#ifdef notyet /* XXXX */
if (uio->uio_lwp)
uio->uio_lwp->l_proc->p_stats->p_ru.ru_msgrcv++;
#endif
KASSERT(m == so->so_rcv.sb_mb);
SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 1");
SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 1");
nextrecord = m->m_nextpkt;
if (pr->pr_flags & PR_ADDR) {
#ifdef DIAGNOSTIC
if (m->m_type != MT_SONAME)
panic("receive 1a");
#endif
orig_resid = 0;
if (flags & MSG_PEEK) {
m = m->m_next;
} else {
sbfree(&so->so_rcv, m);
MFREE(m, so->so_rcv.sb_mb);
m = so->so_rcv.sb_mb;
}
}
while (m && m->m_type == MT_CONTROL && error == 0) {
if (flags & MSG_PEEK) {
m = m->m_next;
} else {
sbfree(&so->so_rcv, m);
MFREE(m, so->so_rcv.sb_mb);
m = so->so_rcv.sb_mb;
}
}
/*
* If m is non-NULL, we have some data to read. From now on,
* make sure to keep sb_lastrecord consistent when working on
* the last packet on the chain (nextrecord == NULL) and we
* change m->m_nextpkt.
*/
if (m) {
if ((flags & MSG_PEEK) == 0) {
m->m_nextpkt = nextrecord;
/*
* If nextrecord == NULL (this is a single chain),
* then sb_lastrecord may not be valid here if m
* was changed earlier.
*/
if (nextrecord == NULL) {
KASSERT(so->so_rcv.sb_mb == m);
so->so_rcv.sb_lastrecord = m;
}
}
type = m->m_type;
if (type == MT_OOBDATA)
flags |= MSG_OOB;
} else {
if ((flags & MSG_PEEK) == 0) {
KASSERT(so->so_rcv.sb_mb == m);
so->so_rcv.sb_mb = nextrecord;
SB_EMPTY_FIXUP(&so->so_rcv);
}
}
SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 2");
SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 2");
moff = 0;
offset = 0;
while (m && resid > 0 && error == 0) {
if (m->m_type == MT_OOBDATA) {
if (type != MT_OOBDATA)
break;
} else if (type == MT_OOBDATA)
break;
#ifdef DIAGNOSTIC
else if (m->m_type != MT_DATA && m->m_type != MT_HEADER)
panic("receive 3");
#endif
so->so_state &= ~SS_RCVATMARK;
len = resid;
if (so->so_oobmark && len > so->so_oobmark - offset)
len = so->so_oobmark - offset;
if (len > m->m_len - moff)
len = m->m_len - moff;
/*
* If mp is set, just pass back the mbufs.
* Otherwise copy them out via the uio, then free.
* Sockbuf must be consistent here (points to current mbuf,
* it points to next record) when we drop priority;
* we must note any additions to the sockbuf when we
* block interrupts again.
*/
resid -= len;
if (len == m->m_len - moff) {
if (m->m_flags & M_EOR)
flags |= MSG_EOR;
if (flags & MSG_PEEK) {
m = m->m_next;
moff = 0;
} else {
nextrecord = m->m_nextpkt;
sbfree(&so->so_rcv, m);
if (mp) {
*mp = m;
mp = &m->m_next;
so->so_rcv.sb_mb = m = m->m_next;
*mp = NULL;
} else {
MFREE(m, so->so_rcv.sb_mb);
m = so->so_rcv.sb_mb;
}
/*
* If m != NULL, we also know that
* so->so_rcv.sb_mb != NULL.
*/
KASSERT(so->so_rcv.sb_mb == m);
if (m) {
m->m_nextpkt = nextrecord;
if (nextrecord == NULL)
so->so_rcv.sb_lastrecord = m;
} else {
so->so_rcv.sb_mb = nextrecord;
SB_EMPTY_FIXUP(&so->so_rcv);
}
SBLASTRECORDCHK(&so->so_rcv,
"kttcp_soreceive 3");
SBLASTMBUFCHK(&so->so_rcv,
"kttcp_soreceive 3");
}
} else {
if (flags & MSG_PEEK)
moff += len;
else {
if (mp)
*mp = m_copym(m, 0, len, M_WAIT);
m->m_data += len;
m->m_len -= len;
so->so_rcv.sb_cc -= len;
}
}
if (so->so_oobmark) {
if ((flags & MSG_PEEK) == 0) {
so->so_oobmark -= len;
if (so->so_oobmark == 0) {
so->so_state |= SS_RCVATMARK;
break;
}
} else {
offset += len;
if (offset == so->so_oobmark)
break;
}
}
if (flags & MSG_EOR)
break;
/*
* If the MSG_WAITALL flag is set (for non-atomic socket),
* we must not quit until "uio->uio_resid == 0" or an error
* termination. If a signal/timeout occurs, return
* with a short count but without error.
* Keep sockbuf locked against other readers.
*/
while (flags & MSG_WAITALL && m == NULL && resid > 0 &&
!sosendallatonce(so) && !nextrecord) {
if (so->so_error || so->so_state & SS_CANTRCVMORE)
break;
/*
* If we are peeking and the socket receive buffer is
* full, stop since we can't get more data to peek at.
*/
if ((flags & MSG_PEEK) && sbspace(&so->so_rcv) <= 0)
break;
/*
* If we've drained the socket buffer, tell the
* protocol in case it needs to do something to
* get it filled again.
*/
if ((pr->pr_flags & PR_WANTRCVD) && so->so_pcb)
(*pr->pr_usrreq)(so, PRU_RCVD, NULL,
(struct mbuf *)(long)flags, NULL, NULL);
SBLASTRECORDCHK(&so->so_rcv,
"kttcp_soreceive sbwait 2");
SBLASTMBUFCHK(&so->so_rcv,
"kttcp_soreceive sbwait 2");
error = sbwait(&so->so_rcv);
if (error) {
sbunlock(&so->so_rcv);
splx(s);
return (0);
}
if ((m = so->so_rcv.sb_mb) != NULL)
nextrecord = m->m_nextpkt;
}
}
if (m && pr->pr_flags & PR_ATOMIC) {
flags |= MSG_TRUNC;
if ((flags & MSG_PEEK) == 0)
(void) sbdroprecord(&so->so_rcv);
}
if ((flags & MSG_PEEK) == 0) {
if (m == NULL) {
/*
* First part is an SB_EMPTY_FIXUP(). Second part
* makes sure sb_lastrecord is up-to-date if
* there is still data in the socket buffer.
*/
so->so_rcv.sb_mb = nextrecord;
if (so->so_rcv.sb_mb == NULL) {
so->so_rcv.sb_mbtail = NULL;
so->so_rcv.sb_lastrecord = NULL;
} else if (nextrecord->m_nextpkt == NULL)
so->so_rcv.sb_lastrecord = nextrecord;
}
SBLASTRECORDCHK(&so->so_rcv, "kttcp_soreceive 4");
SBLASTMBUFCHK(&so->so_rcv, "kttcp_soreceive 4");
if (pr->pr_flags & PR_WANTRCVD && so->so_pcb)
(*pr->pr_usrreq)(so, PRU_RCVD, NULL,
(struct mbuf *)(long)flags, NULL, NULL);
}
if (orig_resid == resid && orig_resid &&
(flags & MSG_EOR) == 0 && (so->so_state & SS_CANTRCVMORE) == 0) {
sbunlock(&so->so_rcv);
splx(s);
goto restart;
}
if (flagsp)
*flagsp |= flags;
release:
sbunlock(&so->so_rcv);
splx(s);
*done = slen - resid;
#if 0
printf("soreceive: error %d slen %llu resid %lld\n", error, slen, resid);
#endif
return (error);
}