| #include <stdint.h> |
| |
| #include "encoding.h" |
| |
| #define get_field(reg, mask) (((reg) & (mask)) / ((mask) & ~((mask) << 1))) |
| #define set_field(reg, mask, val) (((reg) & ~(mask)) | (((val) * ((mask) & ~((mask) << 1))) & (mask))) |
| |
| #if __riscv_xlen == 64 |
| # define SATP_PPN SATP64_PPN |
| typedef uint64_t reg_t; |
| #else |
| # define SATP_PPN SATP32_PPN |
| typedef uint32_t reg_t; |
| #endif |
| |
| static char page_buffer[4096 * 8]; |
| static char *page_buffer_next = page_buffer; |
| |
| typedef struct { |
| unsigned mode; |
| unsigned levels; |
| unsigned ppn_width_bits[5]; |
| unsigned ppn_offset_bits[5]; |
| unsigned entry_width_bytes; |
| unsigned vpn_width_bits; |
| unsigned vaddr_bits; |
| } virtual_memory_system_t; |
| |
| static virtual_memory_system_t sv32 = { |
| .mode = SATP_MODE_SV32, |
| .levels = 2, |
| .ppn_width_bits = {12, 10, 10}, |
| .ppn_offset_bits = {0, 12, 22}, |
| .entry_width_bytes = 4, |
| .vpn_width_bits = 10, |
| .vaddr_bits = 32 |
| }; |
| |
| static virtual_memory_system_t sv39 = { |
| .mode = SATP_MODE_SV39, |
| .levels = 3, |
| .ppn_width_bits = {12, 9, 9, 26}, |
| .ppn_offset_bits = {0, 12, 21, 30}, |
| .entry_width_bytes = 8, |
| .vpn_width_bits = 9, |
| .vaddr_bits = 39 |
| }; |
| |
| static virtual_memory_system_t sv48 = { |
| .mode = SATP_MODE_SV48, |
| .levels = 4, |
| .ppn_width_bits = {12, 9, 9, 9, 26}, |
| .ppn_offset_bits = {0, 12, 21, 30, 39}, |
| .entry_width_bytes = 8, |
| .vpn_width_bits = 9, |
| .vaddr_bits = 48 |
| }; |
| |
| static virtual_memory_system_t *vms; |
| |
| void error() |
| { |
| while (1) |
| ; |
| } |
| |
| void assert(int condition) |
| { |
| if (!condition) |
| error(); |
| } |
| |
| // Return a 4Kb, aligned, page. |
| void *get_page() |
| { |
| page_buffer_next = (char *) (((unsigned long) page_buffer_next + 4095) & ~0xfff); |
| while (page_buffer_next + 4096 >= page_buffer + sizeof(page_buffer)) |
| ; |
| void *result = page_buffer_next; |
| page_buffer_next += 4096; |
| return result; |
| } |
| |
| reg_t entry(char *table, unsigned index) |
| { |
| if (vms->entry_width_bytes == 4) |
| return ((uint32_t *) table)[index]; |
| else if (vms->entry_width_bytes == 8) |
| return ((uint64_t *) table)[index]; |
| else |
| assert(0); |
| } |
| |
| void entry_set(char *table, unsigned index, uint64_t value) |
| { |
| if (vms->entry_width_bytes == 4) |
| ((uint32_t *) table)[index] = value; |
| else if (vms->entry_width_bytes == 8) |
| ((uint64_t *) table)[index] = value; |
| else |
| assert(0); |
| } |
| |
| // Set up 1-to-1 for this entire table. |
| void setup_page_table(char *table, unsigned level, uint64_t physical) |
| { |
| for (unsigned i = 0; i < (1<<vms->vpn_width_bits); i++) { |
| uint64_t pte = PTE_V | PTE_R | PTE_W | PTE_X | PTE_U | PTE_A | PTE_D; |
| // Add in portion of physical address. |
| pte |= physical & (((1LL<<vms->vpn_width_bits)-1) << |
| (PTE_PPN_SHIFT + (level+1) * vms->vpn_width_bits)); |
| // Add in the index. |
| pte |= ((reg_t) i) << (PTE_PPN_SHIFT + level * vms->vpn_width_bits); |
| entry_set(table, i, pte); |
| } |
| } |
| |
| // Return contents of vpn field for the given virtual address and level. |
| unsigned vpn(uint64_t virtual, unsigned level) |
| { |
| virtual >>= 12 + vms->vpn_width_bits * level; |
| return virtual & ((1<<vms->vpn_width_bits)-1); |
| } |
| |
| // Add an entry to the given table, at the given level (0 for 4Kb page). |
| void add_entry(char *table, unsigned level, uint64_t virtual, uint64_t physical) |
| { |
| unsigned current_level = vms->levels - 1; |
| while (1) { |
| unsigned index = vpn(virtual, current_level); |
| if (current_level <= level) { |
| // Add the new entry. |
| entry_set(table, index, PTE_V | PTE_R | PTE_W | PTE_X | PTE_U | PTE_A | PTE_D | |
| ((physical >> 2) & ~((1 << |
| (PTE_PPN_SHIFT + current_level * vms->vpn_width_bits)) - 1))); |
| return; |
| } |
| reg_t pte = entry(table, index); |
| if (!(pte & PTE_V) || |
| ((pte & PTE_R) && (pte & PTE_W))) { |
| // Create a new page |
| void *new_page = get_page(); |
| setup_page_table(new_page, current_level - 1, virtual); |
| entry_set(table, index, PTE_V | PTE_U | PTE_A | PTE_D | |
| ((((reg_t) new_page) >> 2) & ~((1 << 10) - 1))); |
| table = new_page; |
| } else { |
| table = (char *) (pte & ~0xfff); |
| } |
| current_level--; |
| } |
| } |
| |
| int main() |
| { |
| void *master_table = get_page(); |
| setup_page_table(master_table, vms->levels-1, 0); |
| uint32_t *physical = get_page(); |
| //uint32_t *virtual = (uint32_t *) (((reg_t) physical) ^ ((reg_t) 0x40000000)); |
| uint32_t *virtual = (uint32_t *) (((reg_t) physical) ^ (((reg_t) 0xf) << (vms->vaddr_bits - 4))); |
| // Virtual addresses must be sign-extended. |
| if (vms->vaddr_bits < sizeof(virtual) * 8 && (reg_t) virtual & ((reg_t) 1<<(vms->vaddr_bits-1))) |
| virtual = (uint32_t *) ( |
| (reg_t) virtual | ~(((reg_t) 1 << vms->vaddr_bits) - 1)); |
| add_entry(master_table, 0, (reg_t) virtual, (reg_t) physical); |
| |
| unsigned long satp = set_field(0, SATP_MODE, vms->mode); |
| satp = set_field(satp, SATP_PPN, ((unsigned long) master_table) >> 12); |
| write_csr(satp, satp); |
| satp = read_csr(satp); |
| if (get_field(satp, SATP_MODE) != vms->mode) |
| error(); |
| |
| reg_t mstatus = read_csr(mstatus); |
| mstatus |= MSTATUS_MPRV; |
| write_csr(mstatus, mstatus); |
| |
| // Address translation is enabled. |
| physical[0] = 0xdeadbeef; |
| assert(virtual[0] == physical[0]); |
| virtual[1] = 0x55667788; |
| assert(virtual[1] == physical[1]); |
| |
| active: |
| end: |
| while (1) |
| ; |
| } |