/*
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;
}