| // See LICENSE for license details. |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include "riscv_test.h" |
| |
| void trap_entry(); |
| void pop_tf(trapframe_t*); |
| |
| volatile uint64_t tohost; |
| volatile uint64_t fromhost; |
| |
| static void do_tohost(uint64_t tohost_value) |
| { |
| while (tohost) |
| fromhost = 0; |
| tohost = tohost_value; |
| } |
| |
| #define pa2kva(pa) ((void*)(pa) - DRAM_BASE - MEGAPAGE_SIZE) |
| #define uva2kva(pa) ((void*)(pa) - MEGAPAGE_SIZE) |
| |
| #define flush_page(addr) asm volatile ("sfence.vma %0" : : "r" (addr) : "memory") |
| |
| static uint64_t lfsr63(uint64_t x) |
| { |
| uint64_t bit = (x ^ (x >> 1)) & 1; |
| return (x >> 1) | (bit << 62); |
| } |
| |
| static void cputchar(int x) |
| { |
| do_tohost(0x0101000000000000 | (unsigned char)x); |
| } |
| |
| static void cputstring(const char* s) |
| { |
| while (*s) |
| cputchar(*s++); |
| } |
| |
| static void terminate(int code) |
| { |
| do_tohost(code); |
| while (1); |
| } |
| |
| void wtf() |
| { |
| terminate(841); |
| } |
| |
| #define stringify1(x) #x |
| #define stringify(x) stringify1(x) |
| #define assert(x) do { \ |
| if (x) break; \ |
| cputstring("Assertion failed: " stringify(x) "\n"); \ |
| terminate(3); \ |
| } while(0) |
| |
| #define l1pt pt[0] |
| #define user_l2pt pt[1] |
| #if __riscv_xlen == 64 |
| # define NPT 4 |
| #define kernel_l2pt pt[2] |
| # define user_l3pt pt[3] |
| #else |
| # define NPT 2 |
| # define user_l3pt user_l2pt |
| #endif |
| pte_t pt[NPT][PTES_PER_PT] __attribute__((aligned(PGSIZE))); |
| |
| typedef struct { pte_t addr; void* next; } freelist_t; |
| |
| freelist_t user_mapping[MAX_TEST_PAGES]; |
| freelist_t freelist_nodes[MAX_TEST_PAGES]; |
| freelist_t *freelist_head, *freelist_tail; |
| |
| void printhex(uint64_t x) |
| { |
| char str[17]; |
| for (int i = 0; i < 16; i++) |
| { |
| str[15-i] = (x & 0xF) + ((x & 0xF) < 10 ? '0' : 'a'-10); |
| x >>= 4; |
| } |
| str[16] = 0; |
| |
| cputstring(str); |
| } |
| |
| static void evict(unsigned long addr) |
| { |
| assert(addr >= PGSIZE && addr < MAX_TEST_PAGES * PGSIZE); |
| addr = addr/PGSIZE*PGSIZE; |
| |
| freelist_t* node = &user_mapping[addr/PGSIZE]; |
| if (node->addr) |
| { |
| // check accessed and dirty bits |
| assert(user_l3pt[addr/PGSIZE] & PTE_A); |
| uintptr_t sstatus = set_csr(sstatus, SSTATUS_SUM); |
| if (memcmp((void*)addr, uva2kva(addr), PGSIZE)) { |
| assert(user_l3pt[addr/PGSIZE] & PTE_D); |
| memcpy((void*)addr, uva2kva(addr), PGSIZE); |
| } |
| write_csr(sstatus, sstatus); |
| |
| user_mapping[addr/PGSIZE].addr = 0; |
| |
| if (freelist_tail == 0) |
| freelist_head = freelist_tail = node; |
| else |
| { |
| freelist_tail->next = node; |
| freelist_tail = node; |
| } |
| } |
| } |
| |
| void handle_fault(uintptr_t addr, uintptr_t cause) |
| { |
| assert(addr >= PGSIZE && addr < MAX_TEST_PAGES * PGSIZE); |
| addr = addr/PGSIZE*PGSIZE; |
| |
| if (user_l3pt[addr/PGSIZE]) { |
| if (!(user_l3pt[addr/PGSIZE] & PTE_A)) { |
| user_l3pt[addr/PGSIZE] |= PTE_A; |
| } else { |
| assert(!(user_l3pt[addr/PGSIZE] & PTE_D) && cause == CAUSE_STORE_PAGE_FAULT); |
| user_l3pt[addr/PGSIZE] |= PTE_D; |
| } |
| flush_page(addr); |
| return; |
| } |
| |
| freelist_t* node = freelist_head; |
| assert(node); |
| freelist_head = node->next; |
| if (freelist_head == freelist_tail) |
| freelist_tail = 0; |
| |
| uintptr_t new_pte = (node->addr >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V | PTE_U | PTE_R | PTE_W | PTE_X; |
| user_l3pt[addr/PGSIZE] = new_pte | PTE_A | PTE_D; |
| flush_page(addr); |
| |
| assert(user_mapping[addr/PGSIZE].addr == 0); |
| user_mapping[addr/PGSIZE] = *node; |
| |
| uintptr_t sstatus = set_csr(sstatus, SSTATUS_SUM); |
| memcpy((void*)addr, uva2kva(addr), PGSIZE); |
| write_csr(sstatus, sstatus); |
| |
| user_l3pt[addr/PGSIZE] = new_pte; |
| flush_page(addr); |
| |
| __builtin___clear_cache(0,0); |
| } |
| |
| void handle_trap(trapframe_t* tf) |
| { |
| if (tf->cause == CAUSE_USER_ECALL) |
| { |
| int n = tf->gpr[10]; |
| |
| for (long i = 1; i < MAX_TEST_PAGES; i++) |
| evict(i*PGSIZE); |
| |
| terminate(n); |
| } |
| else if (tf->cause == CAUSE_ILLEGAL_INSTRUCTION) |
| { |
| assert(tf->epc % 4 == 0); |
| |
| int* fssr; |
| asm ("jal %0, 1f; fssr x0; 1:" : "=r"(fssr)); |
| |
| if (*(int*)tf->epc == *fssr) |
| terminate(1); // FP test on non-FP hardware. "succeed." |
| else |
| assert(!"illegal instruction"); |
| tf->epc += 4; |
| } |
| else if (tf->cause == CAUSE_FETCH_PAGE_FAULT || tf->cause == CAUSE_LOAD_PAGE_FAULT || tf->cause == CAUSE_STORE_PAGE_FAULT) |
| handle_fault(tf->badvaddr, tf->cause); |
| else |
| assert(!"unexpected exception"); |
| |
| pop_tf(tf); |
| } |
| |
| static void coherence_torture() |
| { |
| // cause coherence misses without affecting program semantics |
| unsigned int random = ENTROPY; |
| while (1) { |
| uintptr_t paddr = DRAM_BASE + ((random % (2 * (MAX_TEST_PAGES + 1) * PGSIZE)) & -4); |
| #ifdef __riscv_atomic |
| if (random & 1) // perform a no-op write |
| asm volatile ("amoadd.w zero, zero, (%0)" :: "r"(paddr)); |
| else // perform a read |
| #endif |
| asm volatile ("lw zero, (%0)" :: "r"(paddr)); |
| random = lfsr63(random); |
| } |
| } |
| |
| void vm_boot(uintptr_t test_addr) |
| { |
| unsigned int random = ENTROPY; |
| if (read_csr(mhartid) > 0) |
| coherence_torture(); |
| |
| _Static_assert(SIZEOF_TRAPFRAME_T == sizeof(trapframe_t), "???"); |
| |
| #if (MAX_TEST_PAGES > PTES_PER_PT) || (DRAM_BASE % MEGAPAGE_SIZE) != 0 |
| # error |
| #endif |
| // map user to lowermost megapage |
| l1pt[0] = ((pte_t)user_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V; |
| // map kernel to uppermost megapage |
| #if __riscv_xlen == 64 |
| l1pt[PTES_PER_PT-1] = ((pte_t)kernel_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V; |
| kernel_l2pt[PTES_PER_PT-1] = (DRAM_BASE/RISCV_PGSIZE << PTE_PPN_SHIFT) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D; |
| user_l2pt[0] = ((pte_t)user_l3pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V; |
| uintptr_t vm_choice = SATP_MODE_SV39; |
| #else |
| l1pt[PTES_PER_PT-1] = (DRAM_BASE/RISCV_PGSIZE << PTE_PPN_SHIFT) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D; |
| uintptr_t vm_choice = SATP_MODE_SV32; |
| #endif |
| write_csr(sptbr, ((uintptr_t)l1pt >> PGSHIFT) | |
| (vm_choice * (SATP_MODE & ~(SATP_MODE<<1)))); |
| |
| // Set up PMPs if present, ignoring illegal instruction trap if not. |
| uintptr_t pmpc = PMP_NAPOT | PMP_R | PMP_W | PMP_X; |
| asm volatile ("la t0, 1f\n\t" |
| "csrrw t0, mtvec, t0\n\t" |
| "csrw pmpaddr0, %1\n\t" |
| "csrw pmpcfg0, %0\n\t" |
| ".align 2\n\t" |
| "1:" |
| : : "r" (pmpc), "r" (-1UL) : "t0"); |
| |
| // set up supervisor trap handling |
| write_csr(stvec, pa2kva(trap_entry)); |
| write_csr(sscratch, pa2kva(read_csr(mscratch))); |
| write_csr(medeleg, |
| (1 << CAUSE_USER_ECALL) | |
| (1 << CAUSE_FETCH_PAGE_FAULT) | |
| (1 << CAUSE_LOAD_PAGE_FAULT) | |
| (1 << CAUSE_STORE_PAGE_FAULT)); |
| // FPU on; accelerator on; allow supervisor access to user memory access |
| write_csr(mstatus, MSTATUS_FS | MSTATUS_XS); |
| write_csr(mie, 0); |
| |
| random = 1 + (random % MAX_TEST_PAGES); |
| freelist_head = pa2kva((void*)&freelist_nodes[0]); |
| freelist_tail = pa2kva(&freelist_nodes[MAX_TEST_PAGES-1]); |
| for (long i = 0; i < MAX_TEST_PAGES; i++) |
| { |
| freelist_nodes[i].addr = DRAM_BASE + (MAX_TEST_PAGES + random)*PGSIZE; |
| freelist_nodes[i].next = pa2kva(&freelist_nodes[i+1]); |
| random = LFSR_NEXT(random); |
| } |
| freelist_nodes[MAX_TEST_PAGES-1].next = 0; |
| |
| trapframe_t tf; |
| memset(&tf, 0, sizeof(tf)); |
| tf.epc = test_addr - DRAM_BASE; |
| pop_tf(&tf); |
| } |