diff --git a/Makefile b/Makefile index 5ed973d..32a8825 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,6 @@ SPIKE_INF_LIB := $(OBJ_DIR)/spike_interface.a #--------------------- user ----------------------- -USER_LDS := user/user.lds USER_CPPS := user/*.c USER_CPPS := $(wildcard $(USER_CPPS)) @@ -71,7 +70,7 @@ USER_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_CPPS))) -USER_TARGET := $(OBJ_DIR)/app_long_loop +USER_TARGET := $(OBJ_DIR)/app_helloworld_no_lds #------------------------targets------------------------ $(OBJ_DIR): @-mkdir -p $(OBJ_DIR) @@ -103,9 +102,9 @@ $(KERNEL_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(SPIKE_INF_LIB) $(KERNEL_OBJS) $(KERNE @$(COMPILE) $(KERNEL_OBJS) $(UTIL_LIB) $(SPIKE_INF_LIB) -o $@ -T $(KERNEL_LDS) @echo "PKE core has been built into" \"$@\" -$(USER_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_OBJS) $(USER_LDS) +$(USER_TARGET): $(OBJ_DIR) $(UTIL_LIB) $(USER_OBJS) @echo "linking" $@ ... - @$(COMPILE) $(USER_OBJS) $(UTIL_LIB) -o $@ -T $(USER_LDS) + @$(COMPILE) --entry=main $(USER_OBJS) $(UTIL_LIB) -o $@ @echo "User app has been built into" \"$@\" -include $(wildcard $(OBJ_DIR)/*/*.d) diff --git a/kernel/config.h b/kernel/config.h index 156ef62..bdc8270 100644 --- a/kernel/config.h +++ b/kernel/config.h @@ -7,17 +7,10 @@ //interval of timer interrupt. added @lab1_3 #define TIMER_INTERVAL 1000000 -#define DRAM_BASE 0x80000000 +// the maximum memory space that PKE is allowed to manage. added @lab2_1 +#define PKE_MAX_ALLOWABLE_RAM 128 * 1024 * 1024 -/* we use fixed physical (also logical) addresses for the stacks and trap frames as in - Bare memory-mapping mode */ -// user stack top -#define USER_STACK 0x81100000 - -// the stack used by PKE kernel when a syscall happens -#define USER_KSTACK 0x81200000 - -// the trap frame used to assemble the user "process" -#define USER_TRAP_FRAME 0x81300000 +// the ending physical address that PKE observes. added @lab2_1 +#define PHYS_TOP (DRAM_BASE + PKE_MAX_ALLOWABLE_RAM) #endif diff --git a/kernel/elf.c b/kernel/elf.c index beb3dff..7d7fa94 100644 --- a/kernel/elf.c +++ b/kernel/elf.c @@ -6,6 +6,8 @@ #include "elf.h" #include "string.h" #include "riscv.h" +#include "vmm.h" +#include "pmm.h" #include "spike_interface/spike_utils.h" typedef struct elf_info_t { @@ -14,11 +16,21 @@ typedef struct elf_info_t { } elf_info; // -// the implementation of allocater. allocates memory space for later segment loading +// the implementation of allocater. allocates memory space for later segment loading. +// this allocater is heavily modified @lab2_1, where we do NOT work in bare mode. // static void *elf_alloc_mb(elf_ctx *ctx, uint64 elf_pa, uint64 elf_va, uint64 size) { - // directly returns the virtual address as we are in the Bare mode in lab1_x - return (void *)elf_va; + elf_info *msg = (elf_info *)ctx->info; + // we assume that size of proram segment is smaller than a page. + kassert(size < PGSIZE); + void *pa = alloc_page(); + if (pa == 0) panic("uvmalloc mem alloc falied\n"); + + memset((void *)pa, 0, PGSIZE); + user_vm_map((pagetable_t)msg->p->pagetable, elf_va, PGSIZE, (uint64)pa, + prot_to_type(PROT_WRITE | PROT_READ | PROT_EXEC, 1)); + + return pa; } // @@ -48,7 +60,7 @@ elf_status elf_init(elf_ctx *ctx, void *info) { } // -// load the elf segments to memory regions as we are in Bare mode in lab1 +// load the elf segments to memory regions. // elf_status elf_load(elf_ctx *ctx) { // elf_prog_header structure is defined in kernel/elf.h diff --git a/kernel/kernel.c b/kernel/kernel.c index ec07e94..d633654 100644 --- a/kernel/kernel.c +++ b/kernel/kernel.c @@ -6,26 +6,71 @@ #include "string.h" #include "elf.h" #include "process.h" - +#include "pmm.h" +#include "vmm.h" +#include "memlayout.h" #include "spike_interface/spike_utils.h" // process is a structure defined in kernel/process.h process user_app; +// +// trap_sec_start points to the beginning of S-mode trap segment (i.e., the entry point of +// S-mode trap vector). added @lab2_1 +// +extern char trap_sec_start[]; + +// +// turn on paging. added @lab2_1 +// +void enable_paging() { + // write the pointer to kernel page (table) directory into the CSR of "satp". + write_csr(satp, MAKE_SATP(g_kernel_pagetable)); + + // refresh tlb to invalidate its content. + flush_tlb(); +} + // // load the elf, and construct a "process" (with only a trapframe). // load_bincode_from_host_elf is defined in elf.c // void load_user_program(process *proc) { - // USER_TRAP_FRAME is a physical address defined in kernel/config.h - proc->trapframe = (trapframe *)USER_TRAP_FRAME; + sprint("User application is loading.\n"); + // allocate a page to store the trapframe. alloc_page is defined in kernel/pmm.c. added @lab2_1 + proc->trapframe = (trapframe *)alloc_page(); memset(proc->trapframe, 0, sizeof(trapframe)); - // USER_KSTACK is also a physical address defined in kernel/config.h - proc->kstack = USER_KSTACK; - proc->trapframe->regs.sp = USER_STACK; + + // allocate a page to store page directory. added @lab2_1 + proc->pagetable = (pagetable_t)alloc_page(); + memset((void *)proc->pagetable, 0, PGSIZE); + + // allocate pages to both user-kernel stack and user app itself. added @lab2_1 + proc->kstack = (uint64)alloc_page() + PGSIZE; //user kernel stack top + uint64 user_stack = (uint64)alloc_page(); //phisical address of user stack bottom + + // USER_STACK_TOP = 0x7ffff000, defined in kernel/memlayout.h + proc->trapframe->regs.sp = USER_STACK_TOP; //virtual address of user stack top + + sprint("user frame 0x%lx, user stack 0x%lx, user kstack 0x%lx \n", proc->trapframe, + proc->trapframe->regs.sp, proc->kstack); // load_bincode_from_host_elf() is defined in kernel/elf.c load_bincode_from_host_elf(proc); + + // populate the page table of user application. added @lab2_1 + // map user stack in userspace, user_vm_map is defined in kernel/vmm.c + user_vm_map((pagetable_t)proc->pagetable, USER_STACK_TOP - PGSIZE, PGSIZE, user_stack, + prot_to_type(PROT_WRITE | PROT_READ, 1)); + + // map trapframe in user space (direct mapping as in kernel space). + user_vm_map((pagetable_t)proc->pagetable, (uint64)proc->trapframe, PGSIZE, (uint64)proc->trapframe, + prot_to_type(PROT_WRITE | PROT_READ, 0)); + + // map S-mode trap vector section in user space (direct mapping as in kernel space) + // here, we assume that the size of usertrap.S is smaller than a page. + user_vm_map((pagetable_t)proc->pagetable, (uint64)trap_sec_start, PGSIZE, (uint64)trap_sec_start, + prot_to_type(PROT_READ | PROT_EXEC, 0)); } // @@ -33,13 +78,22 @@ void load_user_program(process *proc) { // int s_start(void) { sprint("Enter supervisor mode...\n"); - // Note: we use direct (i.e., Bare mode) for memory mapping in lab1. - // which means: Virtual Address = Physical Address - // therefore, we need to set satp to be 0 for now. we will enable paging in lab2_x. - // - // write_csr is a macro defined in kernel/riscv.h + // in the beginning, we use Bare mode (direct) memory mapping as in lab1. + // but now, we are going to switch to the paging mode @lab2_1. + // note, the code still works in Bare mode when calling pmm_init() and kern_vm_init(). write_csr(satp, 0); + // init phisical memory manager + pmm_init(); + + // build the kernel page table + kern_vm_init(); + + // now, switch to paging mode by turning on paging (SV39) + enable_paging(); + // the code now formally works in paging mode, meaning the page table is now in use. + sprint("kernel page table is on \n"); + // the application code (elf) is first loaded into memory, and then put into execution load_user_program(&user_app); diff --git a/kernel/memlayout.h b/kernel/memlayout.h new file mode 100644 index 0000000..48b8018 --- /dev/null +++ b/kernel/memlayout.h @@ -0,0 +1,16 @@ +#ifndef _MEMLAYOUT_H +#define _MEMLAYOUT_H + +// RISC-V machine places its physical memory above DRAM_BASE +#define DRAM_BASE 0x80000000 + +// the beginning virtual address of PKE kernel +#define KERN_BASE 0x80000000 + +// default stack size +#define STACK_SIZE 4096 + +// virtual address of stack top of user process +#define USER_STACK_TOP 0x7ffff000 + +#endif diff --git a/kernel/pmm.c b/kernel/pmm.c new file mode 100644 index 0000000..c10e007 --- /dev/null +++ b/kernel/pmm.c @@ -0,0 +1,88 @@ +#include "pmm.h" +#include "util/functions.h" +#include "riscv.h" +#include "config.h" +#include "util/string.h" +#include "memlayout.h" +#include "spike_interface/spike_utils.h" + +// _end is defined in kernel/kernel.lds, it marks the ending (virtual) address of PKE kernel +extern char _end[]; +// g_mem_size is defined in spike_interface/spike_memory.c, it indicates the size of our +// (emulated) spike machine. g_mem_size's value is obtained when initializing HTIF. +extern uint64 g_mem_size; + +static uint64 free_mem_start_addr; //beginning address of free memory +static uint64 free_mem_end_addr; //end address of free memory (not included) + +typedef struct node { + struct node *next; +} list_node; + +// g_free_mem_list is the head of the list of free physical memory pages +static list_node g_free_mem_list; + +// +// actually creates the freepage list. each page occupies 4KB (PGSIZE), i.e., small page. +// PGSIZE is defined in kernel/riscv.h, ROUNDUP is defined in util/functions.h. +// +static void create_freepage_list(uint64 start, uint64 end) { + g_free_mem_list.next = 0; + for (uint64 p = ROUNDUP(start, PGSIZE); p + PGSIZE < end; p += PGSIZE) + free_page( (void *)p ); +} + +// +// place a physical page at *pa to the free list of g_free_mem_list (to reclaim the page) +// +void free_page(void *pa) { + if (((uint64)pa % PGSIZE) != 0 || (uint64)pa < free_mem_start_addr || (uint64)pa >= free_mem_end_addr) + panic("free_page 0x%lx \n", pa); + + // insert a physical page to g_free_mem_list + list_node *n = (list_node *)pa; + n->next = g_free_mem_list.next; + g_free_mem_list.next = n; +} + +// +// takes the first free page from g_free_mem_list, and returns (allocates) it. +// Allocates only ONE page! +// +void *alloc_page(void) { + list_node *n = g_free_mem_list.next; + if (n) g_free_mem_list.next = n->next; + + return (void *)n; +} + +// +// pmm_init() establishes the list of free physical pages according to available +// physical memory space. +// +void pmm_init() { + // start of kernel program segment + uint64 g_kernel_start = KERN_BASE; + uint64 g_kernel_end = (uint64)&_end; + + uint64 pke_kernel_size = g_kernel_end - g_kernel_start; + sprint("PKE kernel start 0x%lx, PKE kernel end: 0x%lx, PKE kernel size: 0x%lx .\n", + g_kernel_start, g_kernel_end, pke_kernel_size); + + // free memory starts from the end of PKE kernel and must be page-aligined + free_mem_start_addr = ROUNDUP(g_kernel_end , PGSIZE); + + // recompute g_mem_size to limit the physical memory space that our riscv-pke kernel + // needs to manage + g_mem_size = MIN(PKE_MAX_ALLOWABLE_RAM, g_mem_size); + if( g_mem_size < pke_kernel_size ) + panic( "Error when recomputing physical memory size (g_mem_size).\n" ); + + free_mem_end_addr = g_mem_size + DRAM_BASE; + sprint("free physical memory address: [0x%lx, 0x%lx] \n", free_mem_start_addr, + free_mem_end_addr - 1); + + sprint("kernel memory manager is initializing ...\n"); + // create the list of free pages + create_freepage_list(free_mem_start_addr, free_mem_end_addr); +} diff --git a/kernel/pmm.h b/kernel/pmm.h new file mode 100644 index 0000000..bce9075 --- /dev/null +++ b/kernel/pmm.h @@ -0,0 +1,11 @@ +#ifndef _PMM_H_ +#define _PMM_H_ + +// Initialize phisical memeory manager +void pmm_init(); +// Allocate a free phisical page +void* alloc_page(); +// Free an allocated page +void free_page(void* pa); + +#endif \ No newline at end of file diff --git a/kernel/process.c b/kernel/process.c index 5fa8d6f..889dd19 100644 --- a/kernel/process.c +++ b/kernel/process.c @@ -12,12 +12,14 @@ #include "process.h" #include "elf.h" #include "string.h" - +#include "vmm.h" +#include "pmm.h" +#include "memlayout.h" #include "spike_interface/spike_utils.h" //Two functions defined in kernel/usertrap.S extern char smode_trap_vector[]; -extern void return_to_user(trapframe*); +extern void return_to_user(trapframe *, uint64 satp); // current points to the currently running user-mode application. process* current = NULL; @@ -36,7 +38,8 @@ void switch_to(process* proc) { // set up trapframe values (in process structure) that smode_trap_vector will need when // the process next re-enters the kernel. - proc->trapframe->kernel_sp = proc->kstack; // process's kernel stack + proc->trapframe->kernel_sp = proc->kstack; // process's kernel stack + proc->trapframe->kernel_satp = read_csr(satp); // kernel page table proc->trapframe->kernel_trap = (uint64)smode_trap_handler; // SSTATUS_SPP and SSTATUS_SPIE are defined in kernel/riscv.h @@ -51,6 +54,10 @@ void switch_to(process* proc) { // set S Exception Program Counter (sepc register) to the elf entry pc. write_csr(sepc, proc->trapframe->epc); + // make user page table. macro MAKE_SATP is defined in kernel/riscv.h. added @lab2_1 + uint64 user_satp = MAKE_SATP(proc->pagetable); + // return_to_user() is defined in kernel/strap_vector.S. switch to user mode with sret. - return_to_user(proc->trapframe); + // note, return_to_user takes two parameters @ and after lab2_1. + return_to_user(proc->trapframe, user_satp); } diff --git a/kernel/process.h b/kernel/process.h index 1e2fcf8..23fba62 100644 --- a/kernel/process.h +++ b/kernel/process.h @@ -13,18 +13,25 @@ typedef struct trapframe_t { /* offset:256 */ uint64 kernel_trap; // saved user process counter /* offset:264 */ uint64 epc; + + // kernel page table. added @lab2_1 + /* offset:272 */ uint64 kernel_satp; }trapframe; // the extremely simple definition of process, used for begining labs of PKE typedef struct process_t { // pointing to the stack used in trap handling. uint64 kstack; + // user page table + pagetable_t pagetable; // trapframe storing the context of a (User mode) process. trapframe* trapframe; }process; +// switch to run user app void switch_to(process*); +// current running process extern process* current; #endif diff --git a/kernel/riscv.h b/kernel/riscv.h index 419baac..9668580 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -181,4 +181,46 @@ typedef struct riscv_regs_t { /* 240 */ uint64 t6; }riscv_regs; +// following lines are added @lab2_1 +static inline void flush_tlb(void) { asm volatile("sfence.vma zero, zero"); } +#define PGSIZE 4096 // bytes per page +#define PGSHIFT 12 // offset bits within a page + +// use riscv's sv39 page table scheme. +#define SATP_SV39 (8L << 60) +#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) + +#define PTE_V (1L << 0) // valid +#define PTE_R (1L << 1) // readable +#define PTE_W (1L << 2) // writable +#define PTE_X (1L << 3) // executable +#define PTE_U (1L << 4) // 1->user can access, 0->otherwise +#define PTE_G (1L << 5) // global +#define PTE_A (1L << 6) // accessed +#define PTE_D (1L << 7) // dirty + +// shift a physical address to the right place for a PTE. +#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) + +// convert a pte content into its corresponding physical address +#define PTE2PA(pte) (((pte) >> 10) << 12) + +// extract the property bits of a pte +#define PTE_FLAGS(pte) ((pte)&0x3FF) + +// extract the three 9-bit page table indices from a virtual address. +#define PXMASK 0x1FF // 9 bits + +#define PXSHIFT(level) (PGSHIFT + (9 * (level))) +#define PX(level, va) ((((uint64)(va)) >> PXSHIFT(level)) & PXMASK) + +// one beyond the highest possible virtual address. +// MAXVA is actually one bit less than the max allowed by +// Sv39, to avoid having to sign-extend virtual addresses +// that have the high bit set. +#define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) + +typedef uint64 pte_t; +typedef uint64 *pagetable_t; // 512 PTEs + #endif diff --git a/kernel/strap_vector.S b/kernel/strap_vector.S index 100d486..cca99cb 100644 --- a/kernel/strap_vector.S +++ b/kernel/strap_vector.S @@ -37,6 +37,11 @@ smode_trap_vector: # load the address of smode_trap_handler() from p->trapframe->kernel_trap ld t0, 256(a0) + # restore kernel page table from p->trapframe->kernel_satp. added @lab2_1 + ld t1, 272(a0) + csrw satp, t1 + sfence.vma zero, zero + # jump to smode_trap_handler() that is defined in kernel/trap.c jr t0 @@ -48,6 +53,13 @@ smode_trap_vector: # .globl return_to_user return_to_user: + # a0: TRAPFRAME + # a1: user page table, for satp. + + # switch to the user page table. added @lab2_1 + csrw satp, a1 + sfence.vma zero, zero + # [sscratch]=[a0], save a0 in sscratch, so sscratch points to a trapframe now. csrw sscratch, a0 diff --git a/kernel/syscall.c b/kernel/syscall.c index 562245a..acfdeb9 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -10,14 +10,19 @@ #include "string.h" #include "process.h" #include "util/functions.h" - +#include "pmm.h" +#include "vmm.h" #include "spike_interface/spike_utils.h" // // implement the SYS_user_print syscall // ssize_t sys_user_print(const char* buf, size_t n) { - sprint(buf); + // buf is now an address in user space of the given app's user stack, + // so we have to transfer it into phisical address (kernel is running in direct mapping). + assert( current ); + char* pa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), (void*)buf); + sprint(pa); return 0; } diff --git a/kernel/vmm.c b/kernel/vmm.c new file mode 100644 index 0000000..20626a3 --- /dev/null +++ b/kernel/vmm.c @@ -0,0 +1,173 @@ +/* + * virtual address mapping related functions. + */ + +#include "vmm.h" +#include "riscv.h" +#include "pmm.h" +#include "util/types.h" +#include "memlayout.h" +#include "util/string.h" +#include "spike_interface/spike_utils.h" +#include "util/functions.h" + +/* --- utility functions for virtual address mapping --- */ +// +// establish mapping of virtual address [va, va+size] to phyiscal address [pa, pa+size] +// with the permission of "perm". +// +int map_pages(pagetable_t page_dir, uint64 va, uint64 size, uint64 pa, int perm) { + uint64 first, last; + pte_t *pte; + + for (first = ROUNDDOWN(va, PGSIZE), last = ROUNDDOWN(va + size - 1, PGSIZE); + first <= last; first += PGSIZE, pa += PGSIZE) { + if ((pte = page_walk(page_dir, first, 1)) == 0) return -1; + if (*pte & PTE_V) + panic("map_pages fails on mapping va (0x%lx) to pa (0x%lx)", first, pa); + *pte = PA2PTE(pa) | perm | PTE_V; + } + return 0; +} + +// +// convert permission code to permission types of PTE +// +uint64 prot_to_type(int prot, int user) { + uint64 perm = 0; + if (prot & PROT_READ) perm |= PTE_R | PTE_A; + if (prot & PROT_WRITE) perm |= PTE_W | PTE_D; + if (prot & PROT_EXEC) perm |= PTE_X | PTE_A; + if (perm == 0) perm = PTE_R; + if (user) perm |= PTE_U; + return perm; +} + +// +// traverse the page table (starting from page_dir) to find the corresponding pte of va. +// returns: PTE (page table entry) pointing to va. +// +pte_t *page_walk(pagetable_t page_dir, uint64 va, int alloc) { + if (va >= MAXVA) panic("page_walk"); + + // starting from the page directory + pagetable_t pt = page_dir; + + // traverse from page directory to page table. + // as we use risc-v sv39 paging scheme, there will be 3 layers: page dir, + // page medium dir, and page table. + for (int level = 2; level > 0; level--) { + // macro "PX" gets the PTE index in page table of current level + // "pte" points to the entry of current level + pte_t *pte = pt + PX(level, va); + + // now, we need to know if above pte is valid (established mapping to a phyiscal page) + // or not. + if (*pte & PTE_V) { //PTE valid + // phisical address of pagetable of next level + pt = (pagetable_t)PTE2PA(*pte); + } else { //PTE invalid (not exist). + // allocate a page (to be the new pagetable), if alloc == 1 + if( alloc && ((pt = (pte_t *)alloc_page(1)) != 0) ){ + memset(pt, 0, PGSIZE); + // writes the physical address of newly allocated page to pte, to establish the + // page table tree. + *pte = PA2PTE(pt) | PTE_V; + }else //returns NULL, if alloc == 0, or no more physical page remains + return 0; + } + } + + // return a PTE which contains phisical address of a page + return pt + PX(0, va); +} + +// +// look up a virtual page address, return the physical page address or 0 if not mapped. +// +uint64 lookup_pa(pagetable_t pagetable, uint64 va) { + pte_t *pte; + uint64 pa; + + if (va >= MAXVA) return 0; + + pte = page_walk(pagetable, va, 0); + if (pte == 0 || (*pte & PTE_V) == 0 || ((*pte & PTE_R) == 0 && (*pte & PTE_W) == 0)) + return 0; + pa = PTE2PA(*pte); + + return pa; +} + +/* --- kernel page table part --- */ +// _etext is defined in kernel.lds, it points to the address after text and rodata segments. +extern char _etext[]; + +// pointer to kernel page director +pagetable_t g_kernel_pagetable; + +// +// maps virtual address [va, va+sz] to [pa, pa+sz] (for kernel). +// +void kern_vm_map(pagetable_t page_dir, uint64 va, uint64 pa, uint64 sz, int perm) { + // map_pages is defined in kernel/vmm.c + if (map_pages(page_dir, va, sz, pa, perm) != 0) panic("kern_vm_map"); +} + +// +// kern_vm_init() constructs the kernel page table. +// +void kern_vm_init(void) { + // pagetable_t is defined in kernel/riscv.h. it's actually uint64* + pagetable_t t_page_dir; + + // allocate a page (t_page_dir) to be the page directory for kernel. alloc_page is defined in kernel/pmm.c + t_page_dir = (pagetable_t)alloc_page(); + // memset is defined in util/string.c + memset(t_page_dir, 0, PGSIZE); + + // map virtual address [KERN_BASE, _etext] to physical address [DRAM_BASE, DRAM_BASE+(_etext - KERN_BASE)], + // to maintain (direct) text section kernel address mapping. + kern_vm_map(t_page_dir, KERN_BASE, DRAM_BASE, (uint64)_etext - KERN_BASE, + prot_to_type(PROT_READ | PROT_EXEC, 0)); + + sprint("KERN_BASE 0x%lx\n", lookup_pa(t_page_dir, KERN_BASE)); + + // also (direct) map remaining address space, to make them accessable from kernel. + // this is important when kernel needs to access the memory content of user's app + // without copying pages between kernel and user spaces. + kern_vm_map(t_page_dir, (uint64)_etext, (uint64)_etext, PHYS_TOP - (uint64)_etext, + prot_to_type(PROT_READ | PROT_WRITE, 0)); + + sprint("physical address of _etext is: 0x%lx\n", lookup_pa(t_page_dir, (uint64)_etext)); + + g_kernel_pagetable = t_page_dir; +} + +/* --- user page table part --- */ +// +// convert and return the corresponding physical address of a virtual address (va) of +// application. +// +void *user_va_to_pa(pagetable_t page_dir, void *va) { + // TODO (lab2_1): implement user_va_to_pa to convert a given user virtual address "va" + // to its corresponding physical address, i.e., "pa". To do it, we need to walk + // through the page table, starting from its directory "page_dir", to locate the PTE + // that maps "va". If found, returns the "pa" by using: + // pa = PYHS_ADDR(PTE) + (va & (1<