/*
 * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
 *	The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Samuel J. Greear <sjg@evilcode.net>
 *
 * 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. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``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 THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS 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.
 *
 * $DragonFly: $
 */

#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/lwbuf.h>
#include <sys/objcache.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <vm/vm_page.h>
#include <machine/atomic.h>
#include <machine/param.h>

static void lwbuf_init(void *);
SYSINIT(sock_lwb, SI_BOOT2_MACHDEP, SI_ORDER_ANY, lwbuf_init, NULL);

/* Number of pages of KVA to allocate at boot per cpu (2MB) */
#define	LWBUF_BOOT_PAGES	512
/* Number to allocate incrementally (64k) */
#define LWBUF_ALLOC_PAGES	16

static struct objcache *lwbuf_cache;

MALLOC_DEFINE(M_LWBUF, "lwbuf", "Lightweight buffers");
struct objcache_malloc_args lwbuf_malloc_args = { sizeof(struct lwbuf), M_LWBUF };

struct lwbuf_free_kvp {
    vm_offset_t                 kva;
    SLIST_ENTRY(lwbuf_free_kvp) next;
};
SLIST_HEAD(lwbuf_free_kvp_list, lwbuf_free_kvp);

static struct lwbuf_free_kvp_list lwbuf_fpages[SMP_MAXCPU];


static boolean_t
lwbuf_cache_ctor(void *obj, void *pdata, int ocflags)
{
    struct lwbuf *lwb = (struct lwbuf *)obj;

    lwb->kva = NULL;
    lwb->m = NULL;
    lwb->cpumask = 0;

    return (TRUE);
}

static void
lwbuf_initpages(uint32_t cpuid, int pages)
{
    struct lwbuf_free_kvp *free_kvp;
    vm_offset_t k;
    int i;

    k = kmem_alloc_nofault(&kernel_map, PAGE_SIZE * pages);
    for (i = 0; i < pages; ++i) {
        free_kvp = (struct lwbuf_free_kvp *)
            kmalloc(sizeof(*free_kvp), L_LWBUF, M_WAITOK);

        free_kvp->kva = k[i * PAGE_SIZE];
        SLIST_INSERT_HEAD(&lwbuf_fpages[cpuid], free_kvp);
    }
}

static void
lwbuf_init(void *arg)
{
    int i;

    lwbuf_cache = objcache_create("lwbuf", 0, 0,
	lwbuf_cache_ctor, NULL, NULL,
	objcache_malloc_alloc, objcache_malloc_free,
	&lwbuf_malloc_args);

    for (i = 0; i < SMP_MAXCPU; ++i) {
        SLIST_INIT(&lwbuf_fpages[i]);
        lwbuf_initpages(i, LWBUF_BOOT_PAGES);
    }
}

struct lwbuf *
lwbuf_alloc(struct vm_page *m)
{
    struct lwbuf *lwb;
    globaldata_t gd = mycpu;

    if ((lwb = objcache_get(lwbuf_cache, M_WAITOK)) == NULL)
        return (NULL);

    lwb->m = m;

    return (lwb);
}

void
lwbuf_free(struct lwbuf *lwb)
{
    struct lwbuf_free_kvp *free_kvp;

    free_kvp = (struct lwbuf_free_kvp *)
        kmalloc(sizeof(*free_kvp), L_LWBUF, M_WAITOK);
    free_kvp->kva = lwb->kva;
    SLIST_INSERT_HEAD(&lwbuf_fpages[mycpuid], free_kvp);

    lwb->m = NULL;
    lwb->kva = NULL;
    lwb->cpumask = 0;

    objcache_put(lwbuf_cache, lwb);
}

vm_offset_t
lwbuf_kva(struct lwbuf *lwb)
{
    struct lwbuf_free_kvp *free_kvp;
    globaldata_t gd = mycpu;
    cpumask_t old, new;

    if (lwb->cpumask & gd->gd_cpumask == 0) {
        if (lwb->kva == NULL) {
            crit_enter();
check_slist:
            if (!SLIST_EMPTY(&lwbuf_fpages[mycpuid]) {
                free_kvp = SLIST_FIRST(&lwbuf_fpages[mycpuid]);
                SLIST_REMOVE_HEAD(&lwbuf_fpages[mycpuid]);

                lwb->kva = free_kvp->kva;
                kfree(free_kvp, M_LWBUF);
            } else {
                /* XXX: Error check (infinite loop) */
                lwbuf_initpages(mycpuid, LWBUF_ALLOC_PAGES);
                goto check_slist;
            }
            crit_exit();

            pmap_kenter_quick(lwb->kva, lwb->m->phys_addr);
            lwb->cpumask |= gd->gd_cpumask;
        } else {
            pmap_kenter_sync_quick(lwb->kva);

            do {
                old = lwb->cpumask;
                new = old | gd->gd_cpumask;
            } while (atomic_cmpset_int(&lwb->cpumask, old, new) == 0);
        }
    }

    return (lwb->kva);
}
