/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include "../afs/param.h"

RCSID("$Header: /cvs/openafs/src/rx/AIX/rx_knet.c,v 1.6 2001/08/08 02:41:26 shadow Exp $");

#ifdef AFS_AIX41_ENV
#include "../rx/rx_kcommon.h"

static struct protosw parent_proto;	/* udp proto switch */

static void rxk_input(am, hlen)
register struct mbuf *am; {
    register unsigned short *tsp;
    int hdr;
    struct udphdr *tu;
    register struct ip *ti;
    struct udpiphdr *tvu;
    register int i;
    char *phandle;
    long code;
    struct sockaddr_in taddr;
    int tlen;
    short port;
    int data_len, comp_sum;
    /* make sure we have base ip and udp headers in first mbuf */
    if (M_HASCL(am) || am->m_len < 28) {
	am = m_pullup(am, 28);
	if (!am) return;
    }
    hdr = (mtod(am, struct ip *))->ip_hl;
    if (hdr > 5) {
	/* pull up more, the IP hdr is bigger than usual */
	if (am->m_len < (8 + (hdr<<2))) {
	    am = m_pullup(am, 8+(hdr<<2));
	    if (!am) return;
	}
	ti = mtod(am, struct ip	*); /* recompute, since m_pullup allocates new mbuf */
	tu = (struct udphdr *)(((char *)ti) + (hdr<<2)); /* skip ip hdr */
    } else {
	ti = mtod(am, struct ip *);
	tu = (struct udphdr *)(((char *)ti) + 20);	/* skip basic ip hdr */
    }
    
    /* now read the port out */
    port = tu->uh_dport;
    if (port) {
	for(tsp=rxk_ports, i=0; i<MAXRXPORTS;i++) {
	    if (*tsp++ == port) {
		rxk_kpork(am);
		return;
	    }
	}
    }
    /* if we get here, try to deliver packet to udp */
    if (parent_proto.pr_input)
	udp_input(am, hlen);
}

/*
 * the AIX version is complicated by the fact that the internet protocols
 * are in a separate kernel extension, and they are unwilling to export their
 * symbols to us.  We can get there indirectly, however.
 */
#include <net/netisr.h>
static struct ifqueue rxk_q;			/* RXKluge queue	*/
static struct arpcom rxk_bogosity;

/* rxk_kpork -	send pkt over to netwerk kporc for processing */
rxk_kpork(m)
register struct mbuf *m; 
{
    find_input_type(0xdead, m, &rxk_bogosity, 0);
}

/*
 * AIX 4.3.3 changed the type of the second argument to
 * ip_stripoptions().  The ip_stripoptions() prototype is in
 * <netinet/proto_inet.h>.  This header file also acquired a guard
 * macro, _PROTO_INET_H_, at the same time.  So we test for the guard
 * macro to see which type we need to use for the second argument to
 * ip_stripoptions().
 *
 * This way we don't have to introduce a port just to compile AFS on AIX
 * 4.3.3.
 */

#if defined(_PROTO_INET_H_)	/* AIX 4.3.3 and presumably later */
#define	STRIP_ARG2_TYPE	unsigned long
#else				/* AIX 4.3.2 and earlier */
#define	STRIP_ARG2_TYPE	struct mbuf *
#endif

void ip_stripoptions(struct mbuf *m, STRIP_ARG2_TYPE mopt)
{
	struct ip *ip = mtod(m, struct ip *);
	register int i;
	register caddr_t opts;
	int olen;

	olen = (ip->ip_hl<<2) - sizeof (struct ip);
	opts = (caddr_t)(ip + 1);
	i = m->m_len - (sizeof (struct ip) + olen);
	memcpy(opts, opts  + olen, (unsigned)i);
	m->m_len -= olen;
	if (m->m_flags & M_PKTHDR)
		m->m_pkthdr.len -= olen;
	ip->ip_hl = sizeof(struct ip) >> 2;
}

/* rxk_RX_input -	RX pkt input process */
rxk_RX_input(am)
register struct mbuf *am; {
    register unsigned short *tsp;
    int hdr;
    struct udphdr *tu;
    register struct ip *ti;
    struct udpiphdr *tvu;
    register int i;
    char *phandle;
    long code;
    struct sockaddr_in taddr;
    int tlen;
    short port;
    int data_len, comp_sum;

    hdr = (ti = mtod(am, struct ip *))->ip_hl;
    if (hdr > 5) {
	ip_stripoptions(am, 0); /* get rid of anything we don't need */
    }
    tu = (struct udphdr *)(((char *)ti) + 20);
    /*
     * Make mbuf data length reflect UDP length.
     * If not enough data to reflect UDP length, drop.
     */
    tvu = (struct udpiphdr *)ti;
    tlen = ntohs((u_short)tvu->ui_ulen);
    if ((int)ti->ip_len != tlen) {
	if (tlen > (int)ti->ip_len) {
	    m_free(am);
	    return;
	}
	m_adj(am, tlen - (int)ti->ip_len);
    }
    /* deliver packet to rx */
    taddr.sin_family = AF_INET;	    /* compute source address */
    taddr.sin_port = tu->uh_sport;
    taddr.sin_addr.s_addr = ti->ip_src.s_addr;
    /* handle the checksum.  Note that this code damages the actual ip
       header (replacing it with the virtual one, which is the same size),
       so we must ensure we get everything out we need, first */
    if ( tu->uh_sum != 0) {
	/* if the checksum is there, always check it. It's crazy not
	 * to, unless you can really be sure that your
	 * underlying network (and interfaces and drivers and
	 * DMA hardware, etc!) is error-free. First, fill
	 * in entire virtual ip header. */
	tvu->ui_next = 0;
	tvu->ui_prev = 0;
	tvu->ui_x1 = 0;
	tvu->ui_len = tvu->ui_ulen;
	am->m_flags |= M_PKTHDR;
	am->m_pkthdr.len = tlen;
	if (in_cksum(am, sizeof(struct ip) + tlen)) {
	    /* checksum, including cksum field, doesn't come out 0, so
	       this packet is bad */
	    m_freem(am);
	    return;
	}
    }

    /*
     * 28 is IP (20) + UDP (8) header.  ulen includes
     * udp header, and we *don't* tell RX about udp
     * header either.  So, we remove those 8 as well.
     */
    data_len = ntohs(tu->uh_ulen);
    data_len -= 8;
    if (!(*rxk_GetPacketProc)(&phandle, data_len)) {
	if (rx_mb_to_packet(am, m_freem, 28, data_len, phandle)) {
	    /* XXX should just increment counter here.. */
	    printf("rx: truncated UDP packet\n");
	    rxi_FreePacket(phandle);
	} else 
	    (*rxk_PacketArrivalProc)(phandle, &taddr, rx_socket, data_len);
    } else
	m_freem(am);
}

/* rxk_isr - RX Kluge Input Service Routine */
static rxk_isr() {
    register struct mbuf *m;
    IFQ_LOCK_DECL();	/* silly macro has trailing ';'.  Sigh. */
    while (1) {
	IF_DEQUEUE(&rxk_q, m);
	if (!m)	return;
	rxk_RX_input(m);
    }
}

/* 
 * UDP fast timer to raise events for all but Solaris and NCR. 
 * Called about 5 times per second (at unknown priority?).  Must go to
 * splnet or obtain global lock before touching anything significant.
 */
static void rxk_fasttimo (void)
{
    int (*tproc)();
    struct clock temp;

    /* do rx fasttimo processing here */
    rxevent_RaiseEvents(&temp);
    if (tproc = parent_proto.pr_fasttimo) (*tproc)();
}


void rxk_init(void)
{
    register struct protosw *pr;
    extern struct protosw *pffindproto();

    if (!rxk_initDone &&
	(pr = pffindproto(AF_INET, IPPROTO_UDP, SOCK_DGRAM))) {
	parent_proto = *pr;

	pr->pr_input    = rxk_input;
	pr->pr_fasttimo = rxk_fasttimo;


	/*
	 * don't bother with pr_drain and pr_ctlinput
	 * until we have something to do
	 */
	rxk_q.ifq_maxlen = 128;		/* obligatory XXX	*/
	/* add pseudo pkt types as haque to get back onto net kproc */
	if (!add_input_type(0xdead, NET_KPROC, rxk_isr, &rxk_q, NETISR_MAX-1))
		rxk_initDone = 1;
    }

    if (!rxk_initDone) {
	printf("\nAFS: no INTERNET protocol support found\n");
    }
}



void shutdown_rxkernel(void)
{
    register struct protosw *pr;
    register int i;
    extern struct protosw *pffindproto();

    if (rxk_initDone && (pr = pffindproto(AF_INET, IPPROTO_UDP, SOCK_DGRAM))) {
	*pr = parent_proto;

	rxk_initDone = 0;
	for (i=0; i<MAXRXPORTS;i++) {
	    if (rxk_ports[i]) {
		rxk_ports[i] = 0;
		soclose((struct socket *)rxk_portRocks[i]);
		rxk_portRocks[i] = (char *)0;
	    }
	}
    }
}


/* osi_NetSend - 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.
 */
/* XXX In the brave new world, steal the data bufs out of the rx_packet iovec,
 * and just queue those.  XXX
 */

osi_NetSend(asocket, addr, dvec, nvec, asize, istack)
register struct socket *asocket;
struct iovec *dvec;
int nvec;
register afs_int32 asize;
struct sockaddr_in *addr;
int istack;
{
    register struct mbuf *tm, *um;
    register afs_int32 code;
    struct mbuf *top = 0;
    register struct mbuf *m, **mp;
    int len, mlen;
    char *tdata;
    caddr_t tpa;
    int i,tl,rlen;

    AFS_STATCNT(osi_NetSend);
#ifndef	AFS_AIX41_ENV
    /*
     * VRMIX has a version of sun's mclgetx() that works correctly with
     * respect to mcopy(), so we can just dummy up the entire packet as
     * an mbuf cluster, and pass it to the IP output routine (which will
     * most likely have to frag it, but since mclgetx() has been fixed,
     * will work ok).  The only problem is that we have to wait until
     * m_free() has been called on the cluster, to guarantee that we
     * do not muck with it until it has gone out.  We also must refrain
     * from inadvertantly touching a piece of data that falls within the
     * same cache line as any portion of the packet, if we have been lucky
     * enough to be DMA-ing directly out from it.
     * Certain IBM architects assure me that the rios is fast enough
     * that the cost of the extra copy, as opposed to trying to
     * DMA directly from the packet is barely worth my while,
     * but I have a hard time accepting this.
     *
     * We can only use this code once we are passed in an indication of
     * whether we are being called `process-synchronously' or not.
     *
     * of course, the packet must be pinned, which is currently true,
     * but in future may not be.
     */
#endif
    mp = &top;
    i = 0;
    tdata = dvec[0].iov_base;
    tl = dvec[0].iov_len;

    while (1) {
	if (!top) {
            MGETHDR(m, M_DONTWAIT, MT_DATA);
            mlen = MHLEN;
        } else {
            MGET(m, M_DONTWAIT, MT_DATA);
            mlen = MLEN;
        }
        if (!m) {
           /* can't get an mbuf, give up */
           if (top)
              m_freem(top);	/* free mbuf list we're building */
           return 1;
        }
        if (!top) {
           m->m_flags |= M_PKTHDR; /* XXX - temp */
           m->m_pkthdr.len = 0;
           m->m_pkthdr.rcvif = (struct ifnet *)0;
        }

	    /*
	     * WARNING: the `4 * MLEN' is somewhat dubious.  It is better than
	     * `NBPG', which may have no relation to `CLBYTES'.  Also,
	     * `CLBYTES' may be so large that we never use clusters,
	     * resulting in far too many mbufs being used.  It is often
	     * better to briefly use a cluster, even if we are only using a
	     * portion of it.  Since we are on the xmit side, it shouldn't
	     * end up sitting on a queue for a potentially unbounded time
	     * (except perhaps if we are talking to ourself).
	     */
 	    if (asize >= (MHLEN + 3*MLEN)) {
                MCLGET(m,M_DONTWAIT);
            }
            /* now compute usable size */
            if (M_HASCL(m)) {
                len = MIN(m->m_ext.ext_size, asize);
            } else {
		len = MIN(mlen, asize);
            }

	tpa = mtod(m, caddr_t);
	*mp = m;
	mp = &m->m_next;
	m->m_len = 0;
	while (len) {
	  rlen = MIN(len, tl);
	  memcpy(tpa, tdata, rlen);
	  asize -= rlen;
	  len -= rlen;
	  tpa += rlen;
	  m->m_len += rlen;
	  top->m_pkthdr.len += rlen;
	  tdata += rlen;
	  tl -= rlen;
	  if (tl <= 0) {
	    i++;
	    if (i > nvec) {
	      /* shouldn't come here! */
	      asize = 0;   /* so we make progress toward completion */
	      break;
	    }
	    tdata = dvec[i].iov_base;
	    tl = dvec[i].iov_len;
	  }
	}

	if (asize <= 0)
	  break;
        }
	tm = top;

	tm->m_act = (struct mbuf *) 0;

	/* setup mbuf corresponding to destination address */
	MGETHDR(um, M_DONTWAIT, MT_SONAME);
	if (!um) {
	    if (top)
		m_freem(top);	/* free mbuf chain */
	    return 1;
	}
	memcpy(mtod(um, caddr_t), addr, sizeof(*addr));
	um->m_len = sizeof(*addr);
	um->m_pkthdr.len = sizeof(*addr);
	um->m_flags |= M_PKTHDR;

    SOCKET_LOCK(asocket);
    code = (*asocket->so_proto->pr_usrreq)(asocket, PRU_SEND, tm, um, 0);
    SOCKET_UNLOCK(asocket);
    m_free(um);

    return code;
}



#endif /* AFS_AIX41_ENV */
