/*
 * Copyright (C) 1993 AmiTCP/IP Group, <amitcp-group@hut.fi>
 *                    Helsinki University of Technology, Finland.
 *                    All rights reserved.
 * Copyright (C) 2005 Neil Cafferkey
 * Copyright (C) 2005 Pavel Fedin
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 */

#include <conf.h>

#include <aros/asmcall.h>

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <sys/synch.h>

#include <net/if.h>

#if INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#endif

#include <net/if_sana.h>
#include <api/amiga_raf.h>
#define bcopy(a,b,c) CopyMem((APTR)(a),b,c)

/*
 * allocate mbufs for the size MTU at free_chain for read request
 */
#if 0 /* old one */
BOOL
ioip_alloc_mbuf(struct IOIPReq *s2rp, ULONG MTU)
{
    register struct mbuf *m, *n;
    register int len = 0;

    n = s2rp->ioip_reserved;

    /* Check for packet header */
    if(n && (n->m_flags & M_PKTHDR)) {
        /* There is already a full packet  */
        return TRUE;
    }

    /* Prepend by a packet header */
    MGETHDR(m, M_NOWAIT, MT_HEADER);
    if(m) {
        m->m_len = len = MHLEN;
        s2rp->ioip_reserved = m;
        m->m_next = n;

        /* Find the end of the free chain */ /* ASSUME THAT THESE HAVE CLUSTERS */
        while(n = m->m_next) {
            len += n->m_len;
            m = n;
        }

        /*
         * add new (cluster)mbufs to get the desired size
         */
        while(len < MTU) {
            MGET(n, M_NOWAIT, MT_DATA);
            if(n != NULL) {
                MCLGET(n, M_NOWAIT);
                if(n->m_ext.ext_buf != NULL) {
                    n->m_len = n->m_ext.ext_size;
                    len += n->m_ext.ext_size;
                } else {
                    m_free(n);
                    break;
                }
                m = m->m_next = n;
            } else
                break;
        }

        s2rp->ioip_reserved->m_pkthdr.len = len;
    }
    if(len < MTU) {
        m_freem(s2rp->ioip_reserved);
        s2rp->ioip_reserved = NULL;
        return FALSE;
    }

    return TRUE;
}
#else
BOOL
ioip_alloc_mbuf(struct IOIPReq *s2rp, ULONG MTU)
{
    register struct mbuf *m, *n;
    register int len;

    /*
     * s2rp->ioip_reserved is either NULL, or an mbuf chain, possibly having
     * packet header in the first one.
     */
    n = s2rp->ioip_reserved;

    /* Check for packet header */
    if(n && (n->m_flags & M_PKTHDR)) {
        /*
         * chain already has the packet header
         */
        m = n;
        len = m->m_len;
    } else {
        /* Prepend by a packet header */
        MGETHDR(m, M_NOWAIT, MT_HEADER);
        if(m) {
            m->m_len = len = MHLEN;
            s2rp->ioip_reserved = m;
            m->m_next = n;
        } else
            goto fail;
    }
    /*
     * Now m points to the start of the mbuf chain. The first mbuf has
     * a packet header.
     */

    /* Find the end of the free chain */ /* ASSUME THAT THESE HAVE CLUSTERS */
    while(n = m->m_next) {
        len += n->m_len;
        m = n;
    }

    /*
     * Now len has the total length of the mbuf chain, add new
     * (cluster)mbufs to get the desired size (MTU).
     */
    while(len < MTU) {
        MGET(n, M_NOWAIT, MT_DATA);
        if(n != NULL) {
            MCLGET(n, M_NOWAIT);
            if(n->m_ext.ext_buf != NULL) {
                len += n->m_len = n->m_ext.ext_size;
            } else {
                m_free(n);
                goto fail;
            }
            m = m->m_next = n;
        } else
            goto fail;
    }
    s2rp->ioip_reserved->m_pkthdr.len = len;
    return TRUE;

fail:
    m_freem(s2rp->ioip_reserved);
    s2rp->ioip_reserved = NULL;
    return FALSE;
}
#endif

/*
 * Copy data from an mbuf chain starting from the beginning,
 * continuing for "n" bytes, into the indicated continuous buffer.
 *
 * NOTE: this WILL be called from INTERRUPTS, so compile with stack checking
 *       disabled and use __saveds if near data is needed.
 */
/*static SAVEDS BOOL m_copy_from_mbuf(
   REG(a0, BYTE *to),
   REG(a1, struct IOIPReq *from),
   REG(d0, ULONG n))*/
AROS_UFH3(BOOL, m_copy_from_mbuf,
          AROS_UFHA(BYTE *, to, A0),
          AROS_UFHA(struct IOIPReq *, from, A1),
          AROS_UFHA(ULONG, n, D0))
{
    AROS_USERFUNC_INIT
    register struct mbuf *m = from->ioip_packet;
    register unsigned count;

    while(n > 0) {
#if DIAGNOSTIC
        if(m == 0) {
            log(LOG_ERR, "m_copy_from_buff: mbuf chain short");
            return FALSE;
        }
#endif
        count = MIN(m->m_len, n);
        bcopy(mtod(m, caddr_t), to, count);
        n -= count;
        to += count;
        m = m->m_next;
    }
    return TRUE;
    AROS_USERFUNC_EXIT
}

/*
 * Copy data from an continuous buffer 'from' to preallocated mbuf chain
 * starting from the beginning, continuing for "n" bytes.
 * Mbufs in the preallocated chain must have their m_len field set to maximum
 * amount of data that they can have.
 *
 * NOTE: this WILL be called from INTERRUPTS, so compile with stack checking
 *       disabled and use __saveds if near data is needed.
 */
/*static SAVEDS BOOL m_copy_to_mbuf(
   REG(a0, struct IOIPReq* to),
   REG(a1, BYTE *from),
   REG(d0, ULONG n))*/
AROS_UFH3(BOOL, m_copy_to_mbuf,
          AROS_UFHA(struct IOIPReq *, to, A0),
          AROS_UFHA(BYTE *, from, A1),
          AROS_UFHA(ULONG, n, D0))
{
    AROS_USERFUNC_INIT
    register struct mbuf *f, *m = to->ioip_reserved;
    unsigned totlen = n;

#if DIAGNOSTIC
    if(!(m->m_flags & M_PKTHDR)) {
        log(LOG_ERR, "m_copy_to_buff: mbuf chain has no header");
        return FALSE;
    }
#endif

    while(n > 0) {
#if DIAGNOSTIC
        if(m == 0) {
            log(LOG_ERR, "m_copy_to_buff: mbuf chain short, "
                "packet len =%lu, reserved =%lu, "
                "wiretype =%lu, mtu =%lu",
                (u_long)totlen, (u_long)to->ioip_reserved->m_pkthdr.len,
                (u_long)to->ioip_s2.ios2_PacketType,
                (u_long)to->ioip_if->ss_if.if_mtu);
            return FALSE;
        }
#endif
        if(n < m->m_len)
            m->m_len = n;
        bcopy(from, mtod(m, caddr_t), m->m_len);
        from += m->m_len;
        n -= m->m_len;
        if(n > 0)
            m = m->m_next;
    }

    /*
     * move the packet to the field 'ioip_packet',
     * set total length of the packet and terminate it.
     */
    f = m->m_next;		/* first free mbuf */
    m->m_next = NULL;		/* terminate the chain */

    to->ioip_packet = to->ioip_reserved;
    to->ioip_packet->m_pkthdr.len = totlen; /* set packet length */
    to->ioip_reserved = f;		/* leftover mbufs */

    /*
     * More mbuf flags and interface pointer must be set later
     */
    return TRUE;
    AROS_USERFUNC_EXIT
}

struct TagItem buffermanagement[3] = {
    { S2_CopyToBuff, (IPTR)AROS_ASMSYMNAME(m_copy_to_mbuf) },
    { S2_CopyFromBuff, (IPTR)AROS_ASMSYMNAME(m_copy_from_mbuf) },
    { TAG_END, }
};

