Added MMU
This commit is contained in:
		
							
								
								
									
										25
									
								
								src/cpu/exception.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/cpu/exception.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
#include "exception.h"
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
 | 
			
		||||
void exception_trigger(rv32_cpu_t* cpu, uint32_t scause)
 | 
			
		||||
{
 | 
			
		||||
    // An exception can only be triggered by the CPU itself,
 | 
			
		||||
    // so we know we already own the mutex
 | 
			
		||||
    // We are in the CPU thread itself, but we need
 | 
			
		||||
    // the return of this function to be the beginning of
 | 
			
		||||
    // the cpu loop
 | 
			
		||||
    // To achieve that, we can just call cpu_loop (noreturn)
 | 
			
		||||
    // at the end of this function
 | 
			
		||||
 | 
			
		||||
    // Save execution context, so that 'mret/sret/..' can restore it
 | 
			
		||||
    // TODO
 | 
			
		||||
 | 
			
		||||
    // Set PC to STVEC, and set SCAUSE
 | 
			
		||||
    // TODO: If PC cannot be mmu_resolved, throw a 'double fault' ?
 | 
			
		||||
    cpu->pc = cpu->csr[CSR_STVEC];
 | 
			
		||||
    cpu->csr[CSR_SCAUSE] = scause;
 | 
			
		||||
 | 
			
		||||
    pthread_mutex_unlock(&cpu0_mutex);
 | 
			
		||||
    cpu_loop(cpu);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/cpu/exception.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/cpu/exception.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
#ifndef EXCEPTION_H
 | 
			
		||||
#define EXCEPTION_H
 | 
			
		||||
 | 
			
		||||
#include "rv32cpu.h"
 | 
			
		||||
 | 
			
		||||
void exception_trigger(rv32_cpu_t* cpu, uint32_t scause);
 | 
			
		||||
 | 
			
		||||
#define SCAUSE_INSTRUCTION_MISSALIGNED 0x0
 | 
			
		||||
#define SCAUSE_INSTRUCTION_ACCESS_FAULT 0x1
 | 
			
		||||
#define SCAUSE_ILLEGAL_INSTRUCTION 0x2
 | 
			
		||||
#define SCAUSE_BREAKPOINT 0x3
 | 
			
		||||
#define SCAUSE_LOAD_ACCESS_FAULT 0x5
 | 
			
		||||
#define SCAUSE_AMO_ADDRESS_MISALIGNED 0x6
 | 
			
		||||
#define SCAUSE_ENVIRONMENT_CALL 0x8
 | 
			
		||||
#define SCAUSE_INSTRUCTION_PAGE_FAULT 0xC
 | 
			
		||||
#define SCAUSE_LOAD_PAGE_FAULT 0xD
 | 
			
		||||
#define SCAUSE_STORE_AMO_PAGE_FAULT 0xF
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -531,7 +531,7 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
 | 
			
		||||
							switch(instruction->func7)
 | 
			
		||||
							{
 | 
			
		||||
								case FUNC7_SFENCEVMA:
 | 
			
		||||
									fprintf(stderr, "SFENCE.VMA: Guest kernel must think we have an MMU. We have none.\n");
 | 
			
		||||
									// TODO : Check if we really need to do something on SFENCE.VMA ?
 | 
			
		||||
									break;
 | 
			
		||||
								case FUNC7_WFI:
 | 
			
		||||
									fprintf(stderr, "WFI: Guest kernel must think we have interrupts. We have none. Halting simulation.\n");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include "memory.h"
 | 
			
		||||
#include "vriscv.h"
 | 
			
		||||
#include "mmu.h"
 | 
			
		||||
 | 
			
		||||
uint8_t* memory;
 | 
			
		||||
pthread_mutex_t memory_mutex;
 | 
			
		||||
@@ -39,6 +40,8 @@ void mem_register_mmio(uint32_t address, uint32_t reg_size, uint32_t reg_count,
 | 
			
		||||
 | 
			
		||||
void mem_write8(uint32_t address, uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
 | 
			
		||||
	// Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
@@ -68,6 +71,8 @@ void mem_write8(uint32_t address, uint8_t value)
 | 
			
		||||
 | 
			
		||||
void mem_write16(uint32_t address, uint16_t value)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
 | 
			
		||||
	// Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
@@ -97,6 +102,8 @@ void mem_write16(uint32_t address, uint16_t value)
 | 
			
		||||
 | 
			
		||||
void mem_write32(uint32_t address, uint32_t value)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
 | 
			
		||||
	// Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
@@ -126,6 +133,8 @@ void mem_write32(uint32_t address, uint32_t value)
 | 
			
		||||
 | 
			
		||||
uint8_t mem_read8(uint32_t address)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
 | 
			
		||||
    // Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
@@ -155,6 +164,8 @@ uint8_t mem_read8(uint32_t address)
 | 
			
		||||
 | 
			
		||||
uint16_t mem_read16(uint32_t address)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
 | 
			
		||||
    // Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
@@ -184,6 +195,8 @@ uint16_t mem_read16(uint32_t address)
 | 
			
		||||
 | 
			
		||||
uint32_t mem_read32(uint32_t address)
 | 
			
		||||
{
 | 
			
		||||
	address = mmu_resolve(cpu0, address);
 | 
			
		||||
	
 | 
			
		||||
    // Look wether we are on an MMIO region
 | 
			
		||||
	struct MMIO_ENTRY* io = mmio;
 | 
			
		||||
	while(io)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								src/memory/mmu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/memory/mmu.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
#include "mmu.h"
 | 
			
		||||
 | 
			
		||||
#include "cpu/exception.h"
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
extern uint8_t* memory;
 | 
			
		||||
 | 
			
		||||
// Memory Managment Unit implementation
 | 
			
		||||
// We only support Sv32
 | 
			
		||||
 | 
			
		||||
#define PAGE_SIZE (4 * 1024) // 4KiB, 2^12B
 | 
			
		||||
#define LEVELS 2
 | 
			
		||||
#define PTE_SIZE 4 // sizeof(uint32_t)
 | 
			
		||||
 | 
			
		||||
// SATP CSR Register: [MODE(1bit) ASID(9bits) PPN(22bits)]
 | 
			
		||||
#define SATP_MODE (1 << 31)
 | 
			
		||||
#define SATP_MODE_BARE (0)
 | 
			
		||||
#define SATP_MODE_SV32 (1 << 31)
 | 
			
		||||
#define SATP_ASID (0x1FF << 22)
 | 
			
		||||
#define SATP_PPN (0x3FFFFF)
 | 
			
		||||
 | 
			
		||||
// Page Table entry: [PPN[1](12bits) PPN[0](10bits) RSW(2bits) D A G U X W R V]
 | 
			
		||||
#define PTE_PPN_1(pte) ((pte & 0xFFF00000) >> 20)
 | 
			
		||||
#define PTE_PPN_0(pte) ((pte & 0x000FFC00) >> 10)
 | 
			
		||||
#define PTE_PPN(pte) ((pte & 0xFFFFFC00) >> 10)
 | 
			
		||||
#define PTE_RSW (0b11 << 8)
 | 
			
		||||
#define PTE_D (1 << 7)
 | 
			
		||||
#define PTE_A (1 << 6)
 | 
			
		||||
#define PTE_G (1 << 5)
 | 
			
		||||
#define PTE_U (1 << 4)
 | 
			
		||||
#define PTE_X (1 << 3)
 | 
			
		||||
#define PTE_W (1 << 2)
 | 
			
		||||
#define PTE_R (1 << 1)
 | 
			
		||||
#define PTE_V (1 << 0)
 | 
			
		||||
 | 
			
		||||
// Physical address: 34 bits [PPN[1](12bits, ) PPN[0](10bits) Offset(12bits)]
 | 
			
		||||
// We only use 32-bits addresses, so the top 2 are always 0
 | 
			
		||||
#define PADDR_PAGE_OFFSET (0x00000FFF)
 | 
			
		||||
 | 
			
		||||
// Virtual address: [VPN[1](10bits) VPN[0](10bits) Offset(12bits)]
 | 
			
		||||
#define VADDR_VPN_1 (0xFFC00000)
 | 
			
		||||
#define VADDR_VPN_0 (0x003FF000)
 | 
			
		||||
#define VADDR_PAGE_OFFSET (0x00000FFF)
 | 
			
		||||
 | 
			
		||||
uint32_t mmu_resolve(rv32_cpu_t* cpu, uint32_t vaddr)
 | 
			
		||||
{
 | 
			
		||||
	// TODO: Make sure we are in S-mode or U-mode
 | 
			
		||||
 | 
			
		||||
	// Check if MODE field is 'bare', meaning no mmu
 | 
			
		||||
	if((cpu->csr[CSR_SATP] & SATP_MODE) == SATP_MODE_BARE)
 | 
			
		||||
		return vaddr;
 | 
			
		||||
 | 
			
		||||
	// fprintf(stderr, "MMU enabled on (virtual) address 0x%x resolution\n", vaddr);
 | 
			
		||||
 | 
			
		||||
	uint32_t page_table = (cpu->csr[CSR_SATP] & SATP_PPN) * PAGE_SIZE;
 | 
			
		||||
 | 
			
		||||
	// Resolve first-level page table entry
 | 
			
		||||
	uint32_t vpn_1 = (vaddr & VADDR_VPN_1) >> 22;
 | 
			
		||||
	uint32_t pte_address = page_table + vpn_1 * PTE_SIZE;
 | 
			
		||||
	uint32_t pte = *((uint32_t*) (&memory[pte_address]));
 | 
			
		||||
 | 
			
		||||
	if(!(pte & PTE_V))
 | 
			
		||||
	{
 | 
			
		||||
		// Invalid PTE
 | 
			
		||||
		// TODO : Add a MEMORY_ACCESS_TYPE
 | 
			
		||||
		exception_trigger(cpu, SCAUSE_INSTRUCTION_PAGE_FAULT);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if((pte & PTE_R) || (pte & PTE_W) || (pte & PTE_X))
 | 
			
		||||
	{
 | 
			
		||||
		// Leaf PTE, we are ready to resolve the mapping
 | 
			
		||||
		// This is a 4 MiB megapage
 | 
			
		||||
 | 
			
		||||
		// Physical Address: [PPN[1] = pte.PPN[1], PPN[0] = vaddr.VPN[0], offset]
 | 
			
		||||
		uint32_t paddr = 0;
 | 
			
		||||
		paddr |= (PTE_PPN_1(pte) << 22);
 | 
			
		||||
		paddr |= (vaddr & VADDR_VPN_0);
 | 
			
		||||
		paddr |= vaddr & VADDR_PAGE_OFFSET;
 | 
			
		||||
 | 
			
		||||
		return paddr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PTE is a pointer to next level of page table
 | 
			
		||||
	page_table = PTE_PPN(pte) * PAGE_SIZE;
 | 
			
		||||
 | 
			
		||||
	// Resolve second-level page table entry
 | 
			
		||||
	uint32_t vpn_0 = (vaddr & VADDR_VPN_0) >> 12;
 | 
			
		||||
	pte_address = page_table + vpn_0 * PTE_SIZE;
 | 
			
		||||
	pte = *((uint32_t*) (&memory[pte_address]));
 | 
			
		||||
 | 
			
		||||
	if(!(pte & PTE_V))
 | 
			
		||||
	{
 | 
			
		||||
		// Invalid PTE
 | 
			
		||||
		exception_trigger(cpu, SCAUSE_INSTRUCTION_PAGE_FAULT);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// This must be a leaf PTE, as Sv32 only supports 2-level mappings
 | 
			
		||||
	// This is a 4 KiB page
 | 
			
		||||
	if(!((pte & PTE_R) || (pte & PTE_W) || (pte & PTE_X)))
 | 
			
		||||
	{
 | 
			
		||||
		fprintf(stderr, "Error: Pointer second-level Page Table Entry 0x%x at 0x%x while resolving virtual address 0x%x\n", pte, pte_address, vaddr);
 | 
			
		||||
		exit(EXIT_FAILURE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Physical Address: [PPN[1] = pte.PPN[1], PPN[0] = pte.PPN[0], offset]
 | 
			
		||||
	uint32_t paddr = 0;
 | 
			
		||||
	paddr |= (PTE_PPN_1(pte) << 22);
 | 
			
		||||
	paddr |= (PTE_PPN_0(pte) << 12);
 | 
			
		||||
	paddr |= vaddr & VADDR_PAGE_OFFSET;
 | 
			
		||||
 | 
			
		||||
	return paddr;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/memory/mmu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/memory/mmu.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
#ifndef MMU_H
 | 
			
		||||
#define MMU_H
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include "cpu/rv32cpu.h"
 | 
			
		||||
 | 
			
		||||
uint32_t mmu_resolve(rv32_cpu_t* cpu, uint32_t vaddr);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
		Reference in New Issue
	
	Block a user