/*
    P = pte.present
    V = pte.valid
    W = pte.copyonwrite
    D = pte.disk
    N = pti.nailed > 0


    il bit D indica se la pagina ha gia` un indirizzo su disco.
    se D=1 l'indirizzo e` in pti.diskaddr.
    -
    i bit N indicano un contatore di lock, se N=0 la pagina puo`
    essere swappata, altrimenti se N>0 e` lockata.
    -
    il bit W serve per indicare che se si verifica un #PF dovuto
    alla scrittura sulla pagina significa che la pagina era condivisa,
    e che non puo` piu` esserlo.
    -
    il bit V serve per indicare che l'indirizzo e` valido anche se
    il bit P=0.
 ------

 */


#include "clib.h"
#include "i386.h"
#include "phmem.h"
#include "paging.h"
#include "globals.h"


//                             avlGSDACTUWP
// flags standard per pde      000000000011
/*
 * G = global      	S = Size
 * D = dirty      	A = accessed
 * C = Cache Disable    T = write trought
 * U = user level	W = writable
 * P = present
 */


#define VALID_TASK(t) if (t == NULL || (t->cr3 < (pde_t*)0x110000) || \
                          (t->cr3 >= (pde_t*)get_fault_address)) \
                          DEBUG_FATAL("invalid task");



/*
 * *r = l'indirizzo della dword che contiene i flags per la pagina
 * che rappresenta `va'. la pagina puo` essere da 4M o 4K.
 *
 * ritorna EFAULT se l'indirizzo non e` valido, o 0 se ok.
 *
 * - per sapere se la pagina e` da 4M o 4K : pte.size
 * - per sapere se l'indirizzo e` virtuale : pte.present == 0
 * - se ritorna EFAULT, e *pte == NULL indica che la directory
 *   di pagina non e` presente, altrimenti *pte punta comunque
 *   al pte che avra` .present == 0 && .valid == 0
 */
int get_pte_addr(tss_t *task, addr_t va, pte_t** r)
{
	int pdi;

        VALID_TASK(task);

        if (r) *r = NULL;

        pdi = (u32)va >> 22;
        if (task->cr3[pdi].present) {
        	if (task->cr3[pdi].size) {
                	// size 4Mb
        		if (r) *r = (pte_t*)&task->cr3[pdi];
                        return 0;
                } else {
                        // size 4K
                	pte_t* pte;
                        int pti = ((u32)va >> 12) & 0x3FF;

                        pte = (pte_t*)(task->cr3[pdi].pte_addr shl 12);
                        if (r) *r = &pte[pti];
                        if (pte[pti].present || pte[pti].valid) return 0;
                }
        }
	return EFAULT;
}

int va2ph(tss_t* task, addr_t va, phaddr_t* pa)
{
	int r;
        pte_t *pte;
	r = get_pte_addr(task, va, &pte);
        if (r == 0) {
        	if (pte->size == 0)
        	*pa = (phaddr_t)((pte->addr shl 12) + ((u32)va & 0xFFF));
                else
                *pa = (phaddr_t)(((pte->addr & 0xFFC00) shl 12) + ((u32)va & 0x3FFFFF));
        }
        return r;
}


static
phaddr_t alloc_pte(void)
{
	phaddr_t pa;
	pa = alloc_free_pages(2);
        ASSERT(pa != NULL);
        memset(pa, 0, 4096*2);
        DEBUG_VERBOSE("alloc_pte %xh\n", pa);
        return pa + 4096;
}

static
void free_pte(pte_t *pte)
{
	free_pages((phaddr_t) pte2ptinfo(pte), 2);
}


bool valid_va(tss_t* task, addr_t va, size_t len)
{
	int pages;
        VALID_TASK(task);
        if (len == 0) return true;

        for (pages = (len+0xfff) shr 12; pages == 0; pages--, va += 4096) {
		if (get_pte_addr(task, va, NULL) != 0) return false;
        }
        return true;
}


phaddr_t alloc_standard_task_pd(tss_t *task)
{
        u32 i;

        ASSERT(task != NULL);

        task->cr3 = alloc_free_0page();      /* Page Directory */
        if (task->cr3 == NULL) goto _error;

#if 0
	/* FIX [23/09/97] la memoria fisica e` mappata solo per il kernel*/
        /*
         * mappa 1:1 la memoria assegnata al kernel, calcolata come :
         *
         *
         *    ???????? eventuale altra memoria libera
         *    ________ fine memoria assegnata al kernel  @1
         *    HHHHHHHH
         *    ________ fine kernel, inizio memoria gestita da phmem.c
         *    KKKKKKK
         *    _______ stub_stat.kernel_base
         *    ??????  memoria xms usata da altri programmi (puo` essere 0)
         *  1M_______ fine memoria dos
         *
         *  0 _______ inizio memoria DOS
         *
         *   viene mappata tutta la memoria, da 0 fino a @1 (fault_address)
         *
         */

	if (map_memory(task, 0, 0, 0x110, pfWritable) != 0x110) goto _error;
        if (map_memory(task, (addr_t)0x110000, (addr_t)0x110000, 0x1000-0x110,
        	pfWritable+pfGlobal) != (0x1000-0x110)) goto _error;

        pa = get_fault_address(); // in pa l'indirizzo della prima pagina non utilizzabile

        if (pa > 0x3fffff) {
        	r = map_memory4M(task, 0, 0, ((u32)pa + 0x3fffff) shr 22, pfWritable+pfGlobal);
        	if (r == ENOSYS) {
                	/* la cpu non ha le pagine da 4M, usa 4K */
        		r = map_memory(task, 0, 0, ((u32)pa + 0xfff) shr 12, pfWritable+pfGlobal);
        	}
        }

        if (r <= 0) goto _error;
#endif

        /* mappa il kernel :
           utilizza la stessa struttura di pagine utilizzate dal kernel,
           percio` qualunque struttura dinamica creata dal K sara`
           visibile dagli altri task.
        */


        for (i = (u32)VA_KERNEL_BASE shr 22; i < (u32)VA_KERNEL_LIMIT shr 22; i++) {
        	task->cr3[i] = tss_kernel.cr3[i];
        }

        return task->cr3;

_error:
;
	if (task->cr3) free_task_pgtable(task);
        WARNING("return NULL");
        return NULL;

}


void free_task_pgtable(tss_t *task)
{
	pte_t *pte;
        u32 pdi, pti;

        VALID_TASK(task);

        /*
         * libera TUTTE le pagine da 4K che hanno indirizzo < VA_KERNEL_BASE
         *
         */

        for (pdi = 0; pdi < (u32)VA_KERNEL_BASE shr 22; pdi++) {
        	if (task->cr3[pdi].present && !task->cr3[pdi].size) {

                	// solo le pagine presenti da 4K ...

                	pte = (pte_t*)(task->cr3[pdi].pte_addr shl 12);
        		for(pti = 0; pti < 1024; pti++) {
                        	if (pte[pti].present) {
                                	free_page((phaddr_t)(pte[pti].addr shl 12));
                                }
                	}
                        free_pte(pte);
                }
        }
        free_page(task->cr3);
        task->cr3 = 0;
}


addr_t set_memory_limit(tss_t *task, addr_t base, addr_t max_addr)
{
	addr_t va = base;

        VALID_TASK(task);

        /* solo il kernel puo` allocare memoria kernel! :) */
        if (task->cs != KERNEL_CS) {
                if (max_addr > VA_KERNEL_BASE) max_addr = VA_KERNEL_BASE;
        } else {
                if (max_addr > VA_KERNEL_LIMIT) max_addr = VA_KERNEL_LIMIT;
        }

	while (va < max_addr) {
        	int pdi;
                pdi = (u32)va shr 22;
                if (!task->cr3[pdi].present) {
                	task->cr3[pdi].pte_addr = ((u32)alloc_pte() shr 12);
                        /* se non c'e` + memoria esci dal while */
                        if (task->cr3[pdi].pte_addr == 0) break;
                        task->cr3[pdi].present = 1;
                        /* imposta i flag meno restrittivi per la directory,
                           i "veri" flag li hanno i pte.
                        */
                        task->cr3[pdi].writable = 1;
                        task->cr3[pdi].user = 1;
                }
                va += 1 shl 22;
        }
        max_addr = va;

        /*
         * NB: la memoria kernel NON diminuisce mai, e non lo deve fare :
         * la memoria allocata con `valloc' e` marcata come `globale',
         * percio` non puo` essere liberata, a meno di resettare il TLB
         * globale ..
         */
#if 0
	/* FIXME: questo e` da ottimizzare, e da debuggare */
        while (va < VA_KERNEL_BASE) {
        	free_vpages(task, va, 1);
                va += 4096;
        }
#endif

        return max_addr;
}


/*
	alloc_vpages non alloca mai nuovi pte, percio` la memoria
        sara` limitata dalla chiamata a set_memory_limit, che imposta
        il + alto indirizzo utilizzabile.

        [23/09/97] per evitare inutili #PF sarebbe meglio allocare
        veramente la memoria se ce n'e` molta libera, almeno per
        le prime pagine, che di solito sono quelle utilizzate
        subito dopo.
*/

addr_t alloc_vpages(tss_t *task, addr_t at, int npages, page_flags_t pf)
{
        int n = 0;
	pte_t *pte;
        addr_t va_lim;

        VALID_TASK(task);
        if (npages <= 0) return NULL;

        /* limiti diversi per task di cpl 0 e gli altri */
        if (task->cs == KERNEL_CS) va_lim = VA_LIMIT;
        else va_lim = VA_KERNEL_BASE;

        #ifndef NDEBUG
	if (!(task->eflags & F_VM) && at < VA_BASE) {
		WARNING("alloc_vpages at %x for non VM86 task!\n",at);
        }
        #endif

	while (at < va_lim) {

                /* se la directory di pagina e` presente ... */
		if ((get_pte_addr(task, at, &pte) == EFAULT) && pte) {
                	if (++n == npages) {
                                for (; npages > 0; npages--, at-= 4096) {
                                	get_pte_addr(task, at, &pte);
                                        clear_pte_flags(pte, true);

                                        pte->valid = 1;
                                        pte->writable = 1;
                                        if (pf & pfUser)  pte->user = 1;
                                }

                                return at+4096;
                        }
                } else n = 0;
                at += 4096;
        }
        WARNING("return NULL");
        return 0;
}

int free_vpages(tss_t *task, addr_t va, int npages)
{
	if (npages <= 0) return EINVAL;

        VALID_TASK(task);

        if (valid_va(task, va, npages shl 12)) {
		for (; npages > 0; npages--) {
                	pte_t* pte;

                	get_pte_addr(task, va, &pte);
                        if (pte->present) {
                                free_page((phaddr_t)(pte->addr shl 12));
                        }
                        if (pte->disk) {
                        /*
                         * FIXME: bisogna liberare la pagina da disco
                         *
                         */
                        }
                        clear_pte_flags(pte, true);
                }

                return 0;
        } else {
        	WARNING("return EFAULT");
        	return EFAULT;
        }
}


int lock_memory(tss_t* task, addr_t va, size_t size)
{
	if (size == 0) return 0;

	VALID_TASK(task);

        if (valid_va(task, va, size)) {
        	int npages;
		for (npages = (size+0xfff) shr 12; npages == 0; npages--) {
                	pte_t* pte;
                        pti_t* ptif;
                        get_pte_addr(task, va, &pte);
                        ptif = pte2ptinfo(pte);
                        if (ptif->lock < 7) ptif->lock++;
                }
                return 0;
        } else return EFAULT;
}

int unlock_memory(tss_t* task, addr_t va, size_t size)
{
	if (size == 0) return 0;
        VALID_TASK(task);

        if (valid_va(task, va, size)) {
        	int npages;
		for (npages = (size+0xfff) shr 12; npages == 0; npages--) {
                	pte_t* pte;
                        pti_t* ptif;
                        get_pte_addr(task, va, &pte);
                        ptif = pte2ptinfo(pte);
                        if (ptif->lock > 0) ptif->lock--;
                }
                return 0;
        } else return EFAULT;
}


int map_memory(tss_t *task, addr_t va, phaddr_t pa, int npages, page_flags_t pf)
{
        int i, pdi, pti;
        pte_t* pte;

        VALID_TASK(task);

        if (npages < 1) return EINVAL;

        (u32)pa >>= 12;
        (u32)va >>= 12;

	for (i = 0; i < npages; i++) {
                pti = (u32)va & 0x3FF;
		pdi = (u32)va >> 10;

                if (!task->cr3[pdi].present) {
                	phaddr_t ph;
                        ph = alloc_pte();
                        if (ph == 0) {
                        	WARNING("map_memory map only %xh pages\n",i)
                                return i;
                        }
                        clear_pde_flags(&(task->cr3[pdi]));
                        task->cr3[pdi].present = 1;
                        task->cr3[pdi].writable = 1;
                        task->cr3[pdi].user = 1;
                        task->cr3[pdi].pte_addr = (u32)ph shr 12;
                }
                pte = (pte_t*)(task->cr3[pdi].pte_addr shl 12);
                clear_pte_flags(&(pte[pti]), true);
                pte[pti].addr = (dword)pa;
                pte[pti].present = 1;
                if (pf & pfWritable) pte[pti].writable = 1;
                if (pf & pfUser) pte[pti].user = 1;
                if ((pf & pfGlobal) && _cr4.pge) task->cr3[pdi].global = 1;
                va++;
                pa++;
        }
	return i+1;
}

int map_memory4M(tss_t *task, addr_t va, phaddr_t pa, int npages, page_flags_t pf)
{
        int i, pdi;

        VALID_TASK(task);
        if (!_cr4.pse) return ENOSYS;

        if (npages < 1) return EINVAL;


        (u32)pa >>= 12;
        (u32)pa &= ~0x3FF;	// 4Mb align
        (u32)va >>= 22;

	for (i = 0; i < npages; i++) {
		pdi = (u32)va;
                clear_pde_flags(&(task->cr3[pdi]));
                task->cr3[pdi].present = 1;
                task->cr3[pdi].size = 1;
                task->cr3[pdi].pte_addr = (dword)pa;
                if (pf & pfWritable) task->cr3[pdi].writable = 1;
                if (pf & pfUser) task->cr3[pdi].user = 1;
                if (pf & pfGlobal && _cr4.pge) task->cr3[pdi].global = 1;
                va++;
                pa += 1 shl 10;
        }
	return npages;
}


int commit_addr(tss_t *task, addr_t va)
{
	pte_t *pte;
        if (get_pte_addr(task, va, &pte) == 0) {
                phaddr_t ph;
                pti_t *pti;
                if (pte->present == 1) return 0;

                ph = alloc_free_0page();
                if (!ph) return ENOMEM;

                pte->addr = (u32)ph shr 12;
                pte->present = 1;
                pte->dirty = 0;
                if (pte->disk) {
                /*
                 * bisogna leggere le pagina da disco
                 */
                }
                pti = pte2ptinfo(pte);
                pti->counter = 0x80; // contatore 0x80 indica `appena allocata'

                return 0;
        } else {
        	return EFAULT;
        }
}

int uncommit_addr(tss_t *task, addr_t va)
{
	pte_t *pte;

        if (get_pte_addr(task, va, &pte) == 0) {
                pti_t *ptif;
                if (pte->present == 0) return 0;
                ptif = pte2ptinfo(pte);

                if (ptif->lock != 0) return EPERM;

                if (!pte->disk) {
                  /*
                   * spazio per la nuova pagina nello swap file
                   ptif->diskaddr = ...
                   */
                   pte->disk = 1;
                   pte->dirty = 1;
                }

                if (pte->dirty) {
                /*
                 * bisogna scrivere la pagina su disco
                 * all'indirizzo ptif->diskaddr
                 */
                }
                free_page((phaddr_t)((u32)pte->addr shl 12));

                return 0;
        } else return EFAULT;
}


int read_from_task(tss_t* tss, addr_t va, phaddr_t dest, int count)
{
        phaddr_t ph;
        int i,r;

        VALID_TASK(tss);

        /* loop non ottimizzato !*/
	for (i = 0; i < count; i++) {
        	commit_addr(tss, va);
        	r = va2ph(tss, va, &ph);
                if (r != 0) return r;
                *(u8*)dest = *(u8*)ph;
                dest++;
                va++;
        }

        return 0;
}

int write_to_task(tss_t* tss, addr_t va, phaddr_t src, int count)
{
        phaddr_t ph;
        int i,r;
        VALID_TASK(tss);

        /* loop non ottimizzato !*/
	while (count > 0) {
        	i = 4096 - ((u32)va & 0xFFF);
                if (i > count) i = count;
                r = commit_addr(tss, va);
                if (r != 0) goto _ret;
                r = va2ph(tss, va, &ph);
                if (r != 0) goto _ret;
                memcpy(ph, src, i);
                va += i;
                src+= i;
                count -= i;
        }
        return 0;
_ret:
;
	WARNING("return %xh",r);
        return r;
}