/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:osinet.c 12.3$ */
/* $ACIS:osinet.c 12.3$ */
/* $Source: /ibm/acis/usr/sys/afs/RCS/osinet.c,v $ */

#ifndef lint
static char *rcsid = "$Header:osinet.c 12.3$";
#endif

#include "../h/types.h"
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../h/cmap.h"
#include "../h/socket.h"
#include "../h/socketvar.h"
#include "../h/protosw.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/uio.h"
#include "../h/buf.h"
#include "../h/vfs.h"
#include "../h/vnode.h"
#include "../ufs/inode.h"
#include "../netinet/in.h"
#include "../h/mbuf.h"
#include "../h/proc.h"
#include "../h/file.h"
#include "../rpc/types.h"
#include "../afs/osi.h"
#include "../afs/lock.h"

extern int selwait;
extern long afs_debug;
extern struct mbuf *mclgetx();
extern int afs_running;

#ifdef ibm032
extern int maxtextcache;
#endif

long osi_allocs = 0;
static struct osi_packet {
    struct osi_packet *next;
} *freePacketList = 0;

osinet_cleanup() {
    selwait = 0;
#ifdef ibm032
    maxtextcache = 0;
#endif
    osi_allocs = 0;
    freePacketList = 0;
}

/* for mclgetx, called when mbuf chain is freed */
static dummyfun(aparm)
    char *aparm; {
    osi_FreeSendSpace(aparm);
}

/* note that this routine doesn't adjust the mclrefcnt array, so you can't free these buffers
    later.  However, this could be added if we decide to start freeing these things
    someday. */
static char *AllocDouble() {
    struct mbuf tbuf;
    register struct mbuf *tb1, *tb2, **lb;
    int s;

    if (osi_PACKETSIZE <= AFS_CLBYTES) {
	/* try to allocate a cluster mbuf */
	osi_allocs++;
	if (mclget(&tbuf))
	    return mtod(&tbuf, char *);
    }
    else if (osi_PACKETSIZE <= 2*AFS_CLBYTES) {
	/* try to allocate two adjacent cluster mbufs */
	osi_allocs += 2;
	s = splimp();
	if (!mclfree)	/* make sure there's some free clusters */
	    m_clalloc(1, MPG_CLUSTERS);
	lb = &mclfree;
	for(tb1 = *lb; tb1; tb1 = *lb) {
	    tb2 = tb1->m_next;
	    if ((long)tb1 == AFS_CLBYTES + (long)tb2) {
		/* tb2 immediately precedes tb1 in memory */
		*lb = tb2->m_next;  /* pull both dudes out of the list */
		mbstat.m_clfree -= 2;
		splx(s);
		return (char *) tb2;
	    }
	    else if ((long)tb1 + AFS_CLBYTES == (long)tb2) {
		/* tb1 immediately precedes tb2 in memory */
		*lb = tb2->m_next;	/* pull both dudes from list */
		mbstat.m_clfree -= 2;
		splx(s);
		return (char *) tb1;
	    }
	    lb = &tb1->m_next;
	}
	splx(s);
    }
    return (char *) kmem_alloc(osi_PACKETSIZE);
}

/* free packet received by osi_NetReceive.  Now, osi_NetReceive may return either
    a packet ala osi_AllocSendSpace, in which case we want to actually free both the
    mbuf and the data (put in freePacketList), or it may have received an mbuf from
    the driver, in which case we also call the generic mbuf free routine.  What is tricky
    is that packets allocated osi_AllocSendSpace, when freed, do not free their data
    area, since that will be retransmitted again and again (and eventually freed
    using osi_FreeSendSpace), but packets allocated by osi_NetReceive also allocate
    their space from freePacketList, but have a different free procedure that actually
    frees the data space when the mbuf is freed.
*/
osi_FreeRecvBuffer(ambuf)
    register struct mbuf *ambuf; {
    m_free(ambuf);
}

/* free space allocated by AllocSendSpace.  Also called by mclput when freeing a packet
    allocated by osi_NetReceive.
*/
osi_FreeSendSpace(adata)
    register struct osi_packet *adata; {
    adata->next = freePacketList;
    freePacketList = adata;
    return 0;
}

/* osi-layer initialization code */
osi_Init() {
#ifdef ibm032
    maxtextcache = -2;
#endif ibm032
}

/* allocate space for sender */
char *osi_AllocSendSpace() {
    register struct osi_packet *tp;
    if (!freePacketList) return AllocDouble();
    tp = freePacketList;
    freePacketList = tp->next;
    return (char *) tp;
}

/* turn a socket into a greedy socket */
int osi_xgreedy() {
    register struct a {
	int fdes;
    } *uap = (struct a *)u.u_ap;
    register struct file *tf;
    register struct socket *ts;

    tf = getf(uap->fdes);
    if (!tf) {
	u.u_error = EBADF;
	return;
    }
    if (tf->f_type != DTYPE_SOCKET) {
	u.u_error = ENOTSOCK;
	return;
    }
    ts = (struct socket *) tf->f_data;
    u.u_error = soreserve(ts, 32766, 32766);
}

/* allocate a new socket at specified address */
struct osi_socket *osi_NewSocket(aport)
    short aport; {		/* network byte order */
    register long code;
    struct socket *newSocket;
    register struct mbuf *nam;
    struct sockaddr_in myaddr;

    code = socreate(AF_INET, &newSocket, SOCK_DGRAM, 0);
    if (code) return (struct osi_socket *) 0;
    code = soreserve(newSocket, 50000, 50000);
    if (code) soreserve(newSocket, 32766, 32766);
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = aport;
    myaddr.sin_addr.s_addr = 0;
    nam = m_get(M_WAIT, MT_SONAME);
    nam->m_len = sizeof(myaddr);
    bcopy(&myaddr, mtod(nam, caddr_t), sizeof(myaddr));
    code = sobind(newSocket, nam);
    if (code) {
	soclose(newSocket);
	m_freem(nam);
	return (struct osi_socket *) 0;
    }
    return (struct osi_socket *) newSocket;
}

/* free socket allocated by osi_NetSocket */
int osi_FreeSocket(asocket)
    register struct socket *asocket; {
    soclose(asocket);
    return 0;
}

/* send asize bytes at adata from asocket to host at addr.
 *
 * Now, why do we allocate a new buffer when we could theoretically use the one pointed to by adata?
 * Because PRU_SEND returns after queueing the message, not after sending it.  If the sender changes
 * the data after queueing it, we'd see the already-queued data change.  One attempt to fix this
 * without adding a copy would be to have this function wait until the datagram is sent; however this
 * doesn't work well.  In particular, if a host is down, and an ARP fails to that host, this packet
 * will be queued until the ARP request comes back, which could be hours later.  We can't block
 * in this routine that long, since it prevents RPC timeouts from happening.
 */
int osi_NetSend(asocket, addr, adata, asize)
    register struct socket *asocket;
    char *adata;
    register long asize;
    struct sockaddr_in *addr; {
    register struct mbuf *tm, *um;
    register long code;
    char *dbuffer;
    /* setup m_act field in pb->mchain properly */
    if (asize > osi_PACKETSIZE) return -1;
    dbuffer = osi_AllocSendSpace();
    tm = mclgetx(dummyfun, dbuffer, dbuffer, asize, M_WAIT);
    bcopy(adata, dbuffer, asize);
#ifdef AFS_ACTFLAG
    tm->m_act = (struct mbuf *) 1;
#else
#ifndef	vax
    tm->m_act = (struct mbuf *) 0;
#endif
#endif

    /* setup mbuf corresponding to destination address */
    um = m_get(M_WAIT, MT_SONAME);
    if (!um) return 1;
    bcopy(addr, mtod(um, caddr_t), sizeof(*addr));
    um->m_len = sizeof(*addr);
    /* note that udp_usrreq frees funny mbuf.  We hold onto data, but mbuf around
	it is gone.  we free address ourselves.   */
    if (afs_debug & 2)
	afs_dp("sendto: %x.%d\n", mtod(um, struct sockaddr_in *)->sin_addr.s_addr,
	       mtod(um, struct sockaddr_in *)->sin_port);
    code = (*asocket->so_proto->pr_usrreq)(asocket, PRU_SEND, tm, um, 0);
    m_free(um);
    if (code) afs_dp("PRU_SEND code %d\n", code);
    return code;
}

/* receive a packet from asocket, putting address in *addr, and data in adata.
   max size is in *asize, and asize is set to actual size.  Return 0 or error code.
*/
int osi_NetReceive(asocket, addr, ambuf, adata, asize)
    struct socket *asocket;
    struct mbuf **ambuf;
    char **adata;
    long *asize;
    struct sockaddr_in *addr; {
    int priority;
    register long temp;
    register struct sockbuf *tsbuf;
    register struct mbuf *tm, *om, *data;

    sblock(&asocket->so_rcv);
    priority = splnet();
    /* peel off the buffers until we run out, or until we see the m_act switch set on the mbuf
	chain.  Apparently this has changed from a chain to a flag from 4.2 to Sun3/Unix 4.3.
	The m_act switch is set on the address mbuf at the beginnig of every udp packet.
	It is *also* set on the last data packet in an mbuf chain.  Don't forget to
	decrement sb_cc, so that select doesn't think there's stuff left to read. */
    tsbuf = &asocket->so_rcv;
    om = tsbuf->sb_mb;
#ifdef AFS_ACTFLAG
    /* pull packet off chain appropriately */
    sbfree(tsbuf, om);
    for(tm=om->m_next; tm; tm = tm->m_next) {
	if (afs_debug & 2)
	    afs_dp("psize %d, %x %x %x %x %x %x %x %x\n", tm->m_len, mtod(tm, long *)[0], mtod(tm, long *)[1], mtod(tm, long *)[2], mtod(tm, long *)[3], mtod(tm, long *)[4], mtod(tm, long *)[5], mtod(tm, long *)[6], mtod(tm, long *)[7]);
	sbfree(tsbuf, tm);
	if (tm->m_act) break;
    }
    /* tm now points to the last mbuf in the chain.  We set the next guy
	in the socket from this guy's next field, and zero out tm's next field,
	turning our mbuf chain into a packet.
    */
    if (tm) {
	asocket->so_rcv.sb_mb = tm->m_next;
	tm->m_next = (struct mbuf *) 0;
    }
    else panic("afs receive");
#else
#ifdef	vax
   tm=om;
   if (tm && tm->m_type == MT_SONAME){	/* Better be! */
       bcopy((caddr_t) mtod(om, caddr_t), addr, sizeof(*addr));
       data = sbdroprecord(tsbuf);
   }
    for (tm=data; tm; tm=tm->m_next)
	sbfree(tsbuf, tm);
    tsbuf->sb_mb = data->m_act;
#else
    /* adjust socket quota usage */
    for(tm=om; tm; tm = tm->m_next) {
	if (afs_debug & 2)
	    afs_dp("psize %d, %x %x %x %x %x %x %x %x\n", tm->m_len, mtod(tm, long *)[0], mtod(tm, long *)[1], mtod(tm, long *)[2], mtod(tm, long *)[3], mtod(tm, long *)[4], mtod(tm, long *)[5], mtod(tm, long *)[6], mtod(tm, long *)[7]);
	sbfree(tsbuf, tm);
    }
    /* pull packet off chain appropriately; have to admit true 4.3 approach is cleaner */
    tsbuf->sb_mb = om->m_act;
    om->m_act = (struct mbuf *) 0;
#endif vax
#endif
    splx(priority);
    sbunlock(&asocket->so_rcv);
#ifdef	vax
    om = tm = data;
#else
    /* om now points at the whole packet, including address mbuf */
    /* the first mbuf contains only the from address information (16 bytes) */
    bcopy((caddr_t) mtod(om, caddr_t), addr, sizeof(*addr));
    tm = om->m_next;		/* tm is now the first data (rather than addr) mbuf */
#endif
    if (!tm) panic("afsr rdata");
    if (tm->m_next) {
	/* if multiple mbufs, make a copy and return it */
	*adata = osi_AllocSendSpace();
	*ambuf = (struct mbuf *) 0;	    /* means pure data */
	temp = m_cpytoc(tm, 0, 100000, *adata);
	*asize = 100000 - temp;
	m_freem(om);		/* and free all mbufs */
    }
    else {
	/* otherwise we have all the data in one mbuf, so we return it to the user */
#ifndef	vax
/* For Ultrix we've freed it above.. */
	m_free(om);	/* free address mbuf */
#endif
	*ambuf = tm;
	*adata = mtod(tm, char *);
	*asize = tm->m_len;
    }
    return 0;
}

osi_InitWaitHandle (achandle)
    register struct osi_WaitHandle *achandle; {
    achandle->proc = (caddr_t) 0;
}

/* cancel osi_NetWait */
osi_CancelNetWait(achandle)
    struct osi_WaitHandle *achandle; {
    caddr_t proc;
    proc = achandle->proc;
    if (proc == 0) return;
    achandle->proc = (caddr_t) 0;   /* so dude can figure out he was signalled */
    unselect(proc);
}

/* wait for data on asocket, or ams ms later.  asocket may be null.  Returns
    0 if timeout, 1 if data is available and 2 if signalled */
int osi_NetWait(asocket, ams, achandle)
    register struct socket *asocket;
    register struct osi_WaitHandle *achandle;
    long ams; {
    register int code;
    register int priority;

    code = 0;
    if (achandle) achandle->proc = (caddr_t) 0;
    priority = splnet();
    if (asocket) {
	if (soreadable(asocket)) {
	    splx(priority);
	    return 1;
	}
    }
    /* if here, we found no packets.  Wait for timeout or data */
    if (ams == 0) return 0;
    if (asocket) {
	sbselqueue(&asocket->so_rcv);
    }
    if (achandle) achandle->proc = (caddr_t) u.u_procp;
    osi_CallProc(unselect, (char *) u.u_procp, ams);
    sleep(&selwait, PZERO-2);
    osi_CancelProc(unselect, (char *) u.u_procp);
    if (achandle) {
	if (achandle->proc == 0) code =	2;  /* CancelNetWait was called */
	else achandle->proc = (caddr_t) 0;
    }
    /* one last chance for data to have arrived */
    if (asocket && code == 0) {
	if (soreadable(asocket)) {
	    code = 1;
	}
    }
    splx(priority);
    return code;
}
