/* * Copyright (C) 2021 Michael Brown . * * 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 #include #include #include #include "ntloader.h" #include "pmapi.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__) */