blob: cda4c5f46da3df77dd36e0814943cecc56389770 [file] [log] [blame] [raw]
#include "fwcfg.h"
#include "vm.h"
#include "libcflat.h"
static void *free = 0;
static void *vfree_top = 0;
static void free_memory(void *mem, unsigned long size)
{
while (size >= PAGE_SIZE) {
*(void **)mem = free;
free = mem;
mem += PAGE_SIZE;
size -= PAGE_SIZE;
}
}
void *alloc_page()
{
void *p;
if (!free)
return 0;
p = free;
free = *(void **)free;
return p;
}
void free_page(void *page)
{
*(void **)page = free;
free = page;
}
extern char edata;
static unsigned long end_of_memory;
unsigned long *install_pte(unsigned long *cr3,
int pte_level,
void *virt,
unsigned long pte,
unsigned long *pt_page)
{
int level;
unsigned long *pt = cr3;
unsigned offset;
for (level = PAGE_LEVEL; level > pte_level; --level) {
offset = PGDIR_OFFSET((unsigned long)virt, level);
if (!(pt[offset] & PT_PRESENT_MASK)) {
unsigned long *new_pt = pt_page;
if (!new_pt)
new_pt = alloc_page();
else
pt_page = 0;
memset(new_pt, 0, PAGE_SIZE);
pt[offset] = virt_to_phys(new_pt) | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK;
}
pt = phys_to_virt(pt[offset] & PT_ADDR_MASK);
}
offset = PGDIR_OFFSET((unsigned long)virt, level);
pt[offset] = pte;
return &pt[offset];
}
unsigned long *get_pte(unsigned long *cr3, void *virt)
{
int level;
unsigned long *pt = cr3, pte;
unsigned offset;
for (level = PAGE_LEVEL; level > 1; --level) {
offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK;
pte = pt[offset];
if (!(pte & PT_PRESENT_MASK))
return NULL;
if (level == 2 && (pte & PT_PAGE_SIZE_MASK))
return &pt[offset];
pt = phys_to_virt(pte & PT_ADDR_MASK);
}
offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK;
return &pt[offset];
}
unsigned long *install_large_page(unsigned long *cr3,
unsigned long phys,
void *virt)
{
return install_pte(cr3, 2, virt,
phys | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK | PT_PAGE_SIZE_MASK, 0);
}
unsigned long *install_page(unsigned long *cr3,
unsigned long phys,
void *virt)
{
return install_pte(cr3, 1, virt, phys | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK, 0);
}
static void setup_mmu_range(unsigned long *cr3, unsigned long start,
unsigned long len)
{
u64 max = (u64)len + (u64)start;
u64 phys = start;
while (phys + LARGE_PAGE_SIZE <= max) {
install_large_page(cr3, phys, (void *)(ulong)phys);
phys += LARGE_PAGE_SIZE;
}
while (phys + PAGE_SIZE <= max) {
install_page(cr3, phys, (void *)(ulong)phys);
phys += PAGE_SIZE;
}
}
static void setup_mmu(unsigned long len)
{
unsigned long *cr3 = alloc_page();
memset(cr3, 0, PAGE_SIZE);
#ifdef __x86_64__
if (len < (1ul << 32))
len = (1ul << 32); /* map mmio 1:1 */
setup_mmu_range(cr3, 0, len);
#else
if (len > (1ul << 31))
len = (1ul << 31);
/* 0 - 2G memory, 2G-3G valloc area, 3G-4G mmio */
setup_mmu_range(cr3, 0, len);
setup_mmu_range(cr3, 3ul << 30, (1ul << 30));
vfree_top = (void*)(3ul << 30);
#endif
write_cr3(virt_to_phys(cr3));
#ifndef __x86_64__
write_cr4(X86_CR4_PSE);
#endif
write_cr0(X86_CR0_PG |X86_CR0_PE | X86_CR0_WP);
printf("paging enabled\n");
printf("cr0 = %lx\n", read_cr0());
printf("cr3 = %lx\n", read_cr3());
printf("cr4 = %lx\n", read_cr4());
}
void setup_vm()
{
assert(!end_of_memory);
end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE);
free_memory(&edata, end_of_memory - (unsigned long)&edata);
setup_mmu(end_of_memory);
}
void *vmalloc(unsigned long size)
{
void *mem, *p;
unsigned pages;
size += sizeof(unsigned long);
size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
vfree_top -= size;
mem = p = vfree_top;
pages = size / PAGE_SIZE;
while (pages--) {
install_page(phys_to_virt(read_cr3()), virt_to_phys(alloc_page()), p);
p += PAGE_SIZE;
}
*(unsigned long *)mem = size;
mem += sizeof(unsigned long);
return mem;
}
uint64_t virt_to_phys_cr3(void *mem)
{
return (*get_pte(phys_to_virt(read_cr3()), mem) & PT_ADDR_MASK) + ((ulong)mem & (PAGE_SIZE - 1));
}
void vfree(void *mem)
{
unsigned long size = ((unsigned long *)mem)[-1];
while (size) {
free_page(phys_to_virt(*get_pte(phys_to_virt(read_cr3()), mem) & PT_ADDR_MASK));
mem += PAGE_SIZE;
size -= PAGE_SIZE;
}
}
void *vmap(unsigned long long phys, unsigned long size)
{
void *mem, *p;
unsigned pages;
size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
vfree_top -= size;
phys &= ~(unsigned long long)(PAGE_SIZE - 1);
mem = p = vfree_top;
pages = size / PAGE_SIZE;
while (pages--) {
install_page(phys_to_virt(read_cr3()), phys, p);
phys += PAGE_SIZE;
p += PAGE_SIZE;
}
return mem;
}
void *alloc_vpages(ulong nr)
{
vfree_top -= PAGE_SIZE * nr;
return vfree_top;
}
void *alloc_vpage(void)
{
return alloc_vpages(1);
}