/* See COPYRIGHT for copyright information. */
#include <inc/x86.h>
#include <inc/mmu.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>
#include <kern/pmap.h>
#include <kern/kclock.h>
// These variables are set by i386_detect_memory()
static physaddr_t maxpa; // Maximum physical address
size_t npage; // Amount of physical memory (in pages)
static size_t basemem; // Amount of base memory (in bytes)
static size_t extmem; // Amount of extended memory (in bytes)
// These variables are set in i386_vm_init()
pde_t* boot_pgdir; // Virtual address of boot time page directory
physaddr_t boot_cr3; // Physical address of boot time page directory
static char* boot_freemem; // Pointer to next byte of free mem
struct Page* pages; // Virtual address of physical page array
static struct Page_list page_free_list; // Free list of physical pages
// Global descriptor table.
//
// The kernel and user segments are identical (except for the DPL).
// To load the SS register, the CPL must equal the DPL. Thus,
// we must duplicate the segments for the user and the kernel.
//
struct Segdesc gdt[] =
{
// 0x0 - unused (always faults -- for trapping NULL far pointers)
SEG_NULL,
// 0x8 - kernel code segment
[GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0),
// 0x10 - kernel data segment
[GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0),
// 0x18 - user code segment
[GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3),
// 0x20 - user data segment
[GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),
// 0x28 - tss, initialized in idt_init()
[GD_TSS >> 3] = SEG_NULL
};
struct Pseudodesc gdt_pd = {
sizeof(gdt) - 1, (unsigned long) gdt
};
static int
nvram_read(int r)
{
return mc146818_read(r) | (mc146818_read(r + 1) << 8);
}
void
i386_detect_memory(void)
{
// CMOS tells us how many kilobytes there are
basemem = ROUNDDOWN(nvram_read(NVRAM_BASELO)*1024, PGSIZE);
extmem = ROUNDDOWN(nvram_read(NVRAM_EXTLO)*1024, PGSIZE);
// Calculate the maximum physical address based on whether
// or not there is any extended memory. See comment in <inc/mmu.h>.
if (extmem)
maxpa = EXTPHYSMEM + extmem;
else
maxpa = basemem;
npage = maxpa / PGSIZE;
cprintf("Physical memory: %dK available, ", (int)(maxpa/1024));
cprintf("base = %dK, extended = %dK\n", (int)(basemem/1024), (int)(extmem/1024));
}
// --------------------------------------------------------------
// Set up initial memory mappings and turn on MMU.
// --------------------------------------------------------------
static void check_boot_pgdir(void);
static void check_page_alloc();
static void page_check(void);
static void boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, physaddr_t pa, int perm);
//
// A simple physical memory allocator, used only a few times
// in the process of setting up the virtual memory system.
// page_alloc() is the real allocator.
//
// Allocate n bytes of physical memory aligned on an
// align-byte boundary. Align must be a power of two.
// Return kernel virtual address. Returned memory is uninitialized.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list has been set up.
//
static void*
boot_alloc(uint32_t n, uint32_t align)
{
extern char end[];
void *v;
// Initialize boot_freemem if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment -
// i.e., the first virtual address that the linker
// did _not_ assign to any kernel code or global variables.
if (boot_freemem == 0)
boot_freemem = end;
// LAB 2: Your code here:
// Step 1: round boot_freemem up to be aligned properly
boot_freemem=ROUNDUP(boot_freemem,align);
// Step 2: save current value of boot_freemem as allocated chunk
v=(void *)boot_freemem;
// Step 3: increase boot_freemem to record allocation
boot_freemem=boot_freemem+n;
// Step 4: return allocated chunk
return v;
return NULL;
}
// Set up a two-level page table:
// boot_pgdir is its linear (virtual) address of the root
// boot_cr3 is the physical adresss of the root
// Then turn on paging. Then effectively turn off segmentation.
// (i.e., the segment base addrs are set to zero).
//
// This function only sets up the kernel part of the address space
// (ie. addresses >= UTOP). The user part of the address space
// will be setup later.
//
// From UTOP to ULIM, the user is allowed to read but not write.
// Above ULIM the user cannot read (or write).
void
i386_vm_init(void)
{
pde_t* pgdir;
uint32_t cr0;
size_t n;
// Delete this line:
// panic("i386_vm_init: This function is not finished\n");
//////////////////////////////////////////////////////////////////////
// create initial page directory.
pgdir = boot_alloc(PGSIZE, PGSIZE);
memset(pgdir, 0, PGSIZE);
boot_pgdir = pgdir;
boot_cr3 = PADDR(pgdir);
//////////////////////////////////////////////////////////////////////
// Recursively insert PD in itself as a page table, to form
// a virtual page table at virtual address VPT.
// (For now, you don't have understand the greater purpose of the
// following two lines.)
// Permissions: kernel RW, user NONE
pgdir[PDX(VPT)] = PADDR(pgdir)|PTE_W|PTE_P;
// same for UVPT
// Permissions: kernel R, user R
pgdir[PDX(UVPT)] = PADDR(pgdir)|PTE_U|PTE_P;
//////////////////////////////////////////////////////////////////////
// Make 'pages' point to an array of size 'npage' of 'struct Page'.
// The kernel uses this structure to keep track of physical pages;
// 'npage' equals the number of physical pages in memory. User-level
// programs will get read-only access to the array as well.
// You must allocate the array yourself.
// Your code goes here:
n=npage*sizeof(struct Page);
pages=boot_alloc(n,PGSIZE);
//////////////////////////////////////////////////////////////////////
// Now that we've allocated the initial kernel data structures, we set
// up the list of free physical pages. Once we've done so, all further
// memory management will go through the page_* functions. In
// particular, we can now map memory using boot_map_segment or page_insert
page_init();
check_page_alloc();
page_check();
//////////////////////////////////////////////////////////////////////
// Now we set up virtual memory
//////////////////////////////////////////////////////////////////////
// Map 'pages' read-only by the user at linear address UPAGES
// (ie. perm = PTE_U | PTE_P)
// Permissions:
// - pages -- kernel RW, user NONE
// - the read-only version mapped at UPAGES -- kernel R, user R
// Your code goes here:
n=npage*sizeof(struct Page);
boot_map_segment(pgdir,UPAGES,n,PADDR(pages),PTE_U);
//////////////////////////////////////////////////////////////////////
// Map the kernel stack (symbol name "bootstack"). The complete VA
// range of the stack, [KSTACKTOP-PTSIZE, KSTACKTOP), breaks into two
// pieces:
// * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
// * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed => faults
// Permissions: kernel RW, user NONE
// Your code goes here:
boot_map_segment(pgdir,KSTACKTOP-KSTKSIZE,KSTKSIZE,PADDR(bootstack),PTE_W);
//////////////////////////////////////////////////////////////////////
// Map all of physical memory at KERNBASE.
// Ie. the VA range [KERNBASE, 2^32) should map to
// the PA range [0, 2^32 - KERNBASE)
// We might not have 2^32 - KERNBASE bytes of physical memory, but
// we just set up the amapping anyway.
// Permissions: kernel RW, user NONE
// Your code goes here:
boot_map_segment(pgdir,KERNBASE,0xffffffff-KERNBASE,0,PTE_W);
// Check that the initial page directory has been set up correctly.
check_boot_pgdir();
//////////////////////////////////////////////////////////////////////