ntloader/kern/paging.c
2025-02-16 19:52:45 +09:00

265 lines
6.5 KiB
C

/*
* Copyright (C) 2021 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* @file
*
* Paging
*
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "ntloader.h"
#include "e820.h"
#include "paging.h"
#if defined(__i386__) || defined(__x86_64__)
/** Virtual address used as a 2MB window during relocation */
#define COPY_WINDOW 0x200000
/** Paging is available */
int paging;
/** Page directory pointer table */
static uint64_t pdpt[4] __attribute__ ((aligned (PAGE_SIZE)));
/** Page directories */
static uint64_t pd[2048] __attribute__ ((aligned (PAGE_SIZE)));
/**
* Check that paging can be supported
*
* @ret supported Paging can be supported on this CPU
*/
static int paging_supported (void)
{
uint32_t eax;
uint32_t ebx;
uint32_t ecx;
uint32_t edx;
/* Get CPU features */
__asm__ ("cpuid"
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
: "0" (CPUID_FEATURES));
return (edx & CPUID_FEATURE_EDX_PAE);
}
/**
* Map 2MB page directory entry containing address
*
* @v vaddr Virtual address
* @v paddr Physical address
*/
static void map_page (uint32_t vaddr, uint64_t paddr)
{
char *byte = ((char *) (intptr_t) vaddr);
unsigned int index;
/* Sanity checks */
assert ((vaddr & (PAGE_SIZE_2MB - 1)) == 0);
assert ((paddr & (PAGE_SIZE_2MB - 1)) == 0);
/* Populate page directory entry */
index = (vaddr / PAGE_SIZE_2MB);
pd[index] = (paddr | PG_P | PG_RW | PG_US | PG_PS);
/* Invalidate TLB */
__asm__ __volatile__ ("invlpg %0" : : "m" (*byte));
}
/**
* Initialise paging
*
*/
void init_paging (void)
{
uint32_t addr;
unsigned int i;
/* Check for PAE */
if (! paging_supported())
{
DBG ("Paging not possible on this CPU\n");
return;
}
/* Initialise page directory entries */
addr = 0;
do
{
map_page (addr, addr);
addr += PAGE_SIZE_2MB;
} while (addr);
/* Initialise page directory pointer table */
for (i = 0 ; i < (sizeof (pdpt) / sizeof (pdpt[0])) ; i++)
{
addr = ((intptr_t) &pd[ i * PAGE_SIZE / sizeof (pd[0]) ]);
pdpt[i] = (addr | PG_P);
}
/* Mark paging as available */
paging = 1;
}
/**
* Enable paging
*
* @v state Saved paging state to fill in
*/
void enable_paging (struct paging_state *state)
{
unsigned long cr0;
unsigned long cr3;
unsigned long cr4;
/* Do nothing if paging is unavailable */
if (! paging)
return;
/* Save paging state */
__asm__ __volatile__ ("mov %%cr0, %0\n\t"
"mov %%cr3, %1\n\t"
"mov %%cr4, %2\n\t"
: "=r" (cr0), "=r" (cr3), "=r" (cr4));
state->cr0 = cr0;
state->cr3 = cr3;
state->cr4 = cr4;
/* Disable any existing paging */
__asm__ __volatile__ ("mov %0, %%cr0" : : "r" (cr0 & ~CR0_PG));
/* Enable PAE */
__asm__ __volatile__ ("mov %0, %%cr4" : : "r" (cr4 | CR4_PAE));
/* Load page directory pointer table */
__asm__ __volatile__ ("mov %0, %%cr3" : : "r" (pdpt));
/* Enable paging */
__asm__ __volatile__ ("mov %0, %%cr0" : : "r" (cr0 | CR0_PG));
}
/**
* Disable paging
*
* @v state Previously saved paging state
*/
void disable_paging (struct paging_state *state)
{
unsigned long cr0 = state->cr0;
unsigned long cr3 = state->cr3;
unsigned long cr4 = state->cr4;
/* Do nothing if paging is unavailable */
if (! paging)
return;
/* Disable paging */
__asm__ __volatile__ ("mov %0, %%cr0" : : "r" (cr0 & ~CR0_PG));
/* Restore saved paging state */
__asm__ __volatile__ ("mov %2, %%cr4\n\t"
"mov %1, %%cr3\n\t"
"mov %0, %%cr0\n\t"
: : "r" (cr0), "r" (cr3), "r" (cr4));
}
/**
* Relocate data out of 32-bit address space, if possible
*
* @v data Start of data
* @v len Length of data
* @ret start Physical start address
*/
uint64_t relocate_memory_high (void *data, size_t len)
{
intptr_t end = (((intptr_t) data) + len);
struct e820_entry *e820 = NULL;
uint64_t start;
uint64_t dest;
size_t offset;
size_t frag_len;
/* Do nothing if paging is unavailable */
if (! paging)
return ((intptr_t) data);
/* Read system memory map */
while ((e820 = memmap_next (e820)) != NULL)
{
/* Find highest compatible placement within this region */
start = (e820->start + e820->len);
if (start < ADDR_4GB)
continue;
start = (((start - end) & ~(PAGE_SIZE_2MB - 1)) + end);
start -= len;
if (start < e820->start)
continue;
if (start < ADDR_4GB)
continue;
/* Relocate to this region */
dest = start;
while (len)
{
/* Calculate length within this 2MB page */
offset = (((intptr_t) data) &
(PAGE_SIZE_2MB - 1));
frag_len = (PAGE_SIZE_2MB - offset);
if (frag_len > len)
frag_len = len;
/* Map copy window to destination */
map_page (COPY_WINDOW,
(dest & ~(PAGE_SIZE_2MB - 1)));
/* Copy data through copy window */
memcpy ((((void *) COPY_WINDOW) + offset),
data, frag_len);
/* Map original page to destination */
map_page ((((intptr_t) data) - offset),
(dest & ~(PAGE_SIZE_2MB - 1)));
/* Move to next 2MB page */
data += frag_len;
dest += frag_len;
len -= frag_len;
}
/* Remap copy window */
map_page (COPY_WINDOW, COPY_WINDOW);
return start;
}
/* Leave at original location */
return ((intptr_t) data);
}
#endif /* defined(__i386__) || defined(__x86_64__) */