/*	$NetBSD: pdsim.c,v 1.2 2006/10/14 04:43:41 yamt Exp $	*/

/*-
 * Copyright (c)2006 YAMAMOTO Takashi,
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */

#include "pdsim.h"

#define	SHOWFAULT
#if defined(SHOWQLEN) || defined(SHOWIRR)
#undef SHOWFAULT
#endif

#undef	READAHEAD

struct vm_page *pages;

struct uvmexp uvmexp;

int npagein;
int nfault;
int raio;
int rahit;

int lastacc[MAXID];
int irr[MAXID];
int ts;

struct {
	int fault;
	int hit;
} stats[MAXID];

TAILQ_HEAD(, vm_page) freeq;

struct vm_page *
pdsim_pagealloc(struct uvm_object *obj, int idx)
{
	struct vm_page *pg;

	pg = TAILQ_FIRST(&freeq);
	if (pg == NULL) {
		return NULL;
	}
	TAILQ_REMOVE(&freeq, pg, pageq);
	pg->offset = idx << PAGE_SHIFT;
	pg->uanon = NULL;
	pg->uobject = obj;
	pg->pqflags = 0;
	obj->pages[idx] = pg;
	uvmexp.free--;
	uvmexp.filepages++;

	return pg;
}

void
pdsim_pagefree(struct vm_page *pg)
{
	struct uvm_object *obj;

	KASSERT(pg != NULL);

#if defined(SHOWFREE)
	if (pg->offset != -1) {
		int idx = pg->offset >> PAGE_SHIFT;
		printf("%d %d	# FREE IRR\n", idx, irr[idx]);
	}
#endif /* defined(SHOWFREE) */

	uvmpdpol_pagedequeue(pg);

	KASSERT(pg->uanon == NULL);
	obj = pg->uobject;
	if (obj != NULL) {
		int idx;

		idx = pg->offset >> PAGE_SHIFT;
		KASSERT(obj->pages[idx] == pg);
		obj->pages[idx] = NULL;
		uvmexp.filepages--;
	}
	TAILQ_INSERT_HEAD(&freeq, pg, pageq);
	uvmexp.free++;
}

static struct vm_page *
pdsim_pagelookup(struct uvm_object *obj, int index)
{
	struct vm_page *pg;

	pg = obj->pages[index];

	return pg;
}

static void
pdsim_pagemarkreferenced(struct vm_page *pg)
{

	pg->_mdflags |= MDPG_REFERENCED;
}

boolean_t
pmap_is_referenced(struct vm_page *pg)
{

	return pg->_mdflags & MDPG_REFERENCED;
}

boolean_t
pmap_clear_reference(struct vm_page *pg)
{
	boolean_t referenced = pmap_is_referenced(pg);

	pg->_mdflags &= ~MDPG_REFERENCED;

	return referenced;
}

static void
pdsim_init(int n)
{
	struct vm_page *pg;
	int i;

	uvmpdpol_init();
	uvmexp.npages = n;
	uvmpdpol_reinit();

	TAILQ_INIT(&freeq);
	pages = calloc(n, sizeof(*pg));
	for (i = 0; i < n; i++) {
		pg = &pages[i];
		pg->offset = -1;
		pdsim_pagefree(pg);
	}
}

static void
pdsim_reclaimone(void)
{
	struct vm_page *pg;

	uvmexp.freetarg = 1;
	while (uvmexp.free < uvmexp.freetarg) {
		uvmpdpol_tune();
		uvmpdpol_scaninit();
		pg = uvmpdpol_selectvictim();
		if (pg != NULL) {
			pdsim_pagefree(pg);
		}
		uvmpdpol_balancequeue(0);
	}
}

static void
fault(struct uvm_object *obj, int index)
{
	struct vm_page *pg;

	DPRINTF("fault: %d -> ", index);
	nfault++;
	ts++;
	if (lastacc[index]) {
		irr[index] = ts - lastacc[index];
	}
	lastacc[index] = ts;
	stats[index].fault++;
	pg = pdsim_pagelookup(obj, index);
	if (pg) {
		DPRINTF("cached\n");
		pdsim_pagemarkreferenced(pg);
		stats[index].hit++;
		if ((pg->_mdflags & MDPG_SPECULATIVE) != 0) {
			pg->_mdflags &= ~MDPG_SPECULATIVE;
			rahit++;
		}
		return;
	}
	DPRINTF("miss\n");
retry:
	pg = pdsim_pagealloc(obj, index);
	if (pg == NULL) {
		pdsim_reclaimone();
		goto retry;
	}
	npagein++;
#if defined(SHOWFAULT)
	printf("%d	# FLT\n", index);
#endif
	pdsim_pagemarkreferenced(pg);
	uvmpdpol_pageactivate(pg);
	uvmpdpol_pageactivate(pg);
	dump("fault");
#if defined(READAHEAD)
	pg = pdsim_pagelookup(obj, index + 1);
	if (pg == NULL) {
ra_retry:
		pg = pdsim_pagealloc(obj, index + 1);
		if (pg == NULL) {
			pdsim_reclaimone();
			goto ra_retry;
		}
		raio++;
		pg->_mdflags |= MDPG_SPECULATIVE;
#if defined(SHOWFAULT)
		printf("%d	# READ-AHEAD\n", index + 1);
#endif
	}
	uvmpdpol_pageenqueue(pg);
	dump("read-ahead");
#endif /* defined(READAHEAD) */
}

struct uvm_object obj;

static void
test(void)
{
	memset(&obj, 0, sizeof(obj));
	char *ln;

	for (;; free(ln)) {
		int i;
		int ch;

		ln = fparseln(stdin, NULL, NULL, NULL, 0); 
		if (ln == NULL) {
			break;
		}
		ch = *ln;
		if (ch == '\0') {
			break;
		}
		if (ch == 'd') {
			dump("test");
			continue;
		}
		i = atoi(ln);
		fault(&obj, i);
#if defined(SHOWQLEN)
		showqlen();
#endif
	}
}

#if defined(DEBUG)
static void
dumpstats(void)
{
	int i;
	for (i = 0; i < MAXID; i++) {
		if (stats[i].fault == 0) {
			continue;
		}
		DPRINTF("[%d] %d/%d %d\n", i,
		    stats[i].hit, stats[i].fault, irr[i]);
	}
}
#endif /* defined(DEBUG) */

int
main(int argc, char *argv[])
{

	setvbuf(stderr, NULL, _IOFBF, 0); /* XXX */

	pdsim_init(atoi(argv[1]));
	test();
	DPRINTF("io %d (%d + ra %d) / flt %d\n",
	    npagein + raio, npagein, raio, nfault);
	DPRINTF("rahit / raio= %d / %d\n", rahit, raio);
#if defined(DEBUG)
	dumpstats();
#endif
	exit(0);
}
