Skip to content

Commit

Permalink
Unify MMU translation APIs
Browse files Browse the repository at this point in the history
To enable guest OS to run SDL applications using bidirectional queues,
the VMM or system emulator must access the guest OS's virtual memory,
as the queues are defined there. This requires MMU translation. In
addition, the queues event data size likely larger than a word (maybe a
page), thus the existing rv->io.mem_{read,write}_w do not sufficient for
such memory manipulation.

Export rv->io.mem_translate() to perform gVA2gPA translation. Unifying
MMU translation logic by using rv->io.mem_translate(), eliminating
redundant flows:
mmu_walk -> check_pg_fault -> check_signal -> get_ppn_and_offset.
Also, this interface allowing src/syscall_sdl.c to handle virtual
memory by first translating the gVA2gPA, followed by memory_{read/write}
the chunk of data at once.

TODO: dTLB can be introduced in rv->io.mem_trans() to cache the gVA2gPA
translation.

See: sysprog21#310, sysprog21#510
  • Loading branch information
ChinYikMing committed Jan 31, 2025
1 parent 78d2977 commit 9ff0244
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 98 deletions.
18 changes: 17 additions & 1 deletion src/riscv.h
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,20 @@ typedef void (*riscv_mem_write_s)(riscv_t *rv,
typedef void (*riscv_mem_write_b)(riscv_t *rv,
riscv_word_t addr,
riscv_byte_t data);
#if RV32_HAS(SYSTEM)
/*
* VA2PA handler
* The MMU walkers and fault checkers are defined in system.c
* Thus, exporting this handler through function pointer
* preserves the encapsulation of MMU translation.
*
* ifetch do not leverage this translation because basic block
* might be retranslated and the corresponding PTE is NULL.
*/
typedef riscv_word_t (*riscv_mem_translate_t)(riscv_t *rv,
riscv_word_t vaddr,
bool rw);
#endif

/* system instruction handlers */
typedef void (*riscv_on_ecall)(riscv_t *rv);
Expand All @@ -424,7 +438,9 @@ typedef struct {
riscv_mem_write_s mem_write_s;
riscv_mem_write_b mem_write_b;

/* TODO: add peripheral I/O interfaces */
#if RV32_HAS(SYSTEM)
riscv_mem_translate_t mem_translate;
#endif

/* system */
riscv_on_ecall on_ecall;
Expand Down
196 changes: 99 additions & 97 deletions src/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include "devices/uart.h"
#include "riscv_private.h"

#define R 1
#define W 0

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
void emu_update_uart_interrupts(riscv_t *rv)
{
Expand Down Expand Up @@ -283,6 +286,12 @@ MMU_FAULT_CHECK_IMPL(write, pagefault_store)
extern bool need_retranslate;
static uint32_t mmu_ifetch(riscv_t *rv, const uint32_t addr)
{
/*
* Do not call rv->io.mem_translate() because the basic block might be
* retranslated and the corresponding PTE is NULL, get_ppn_and_offset()
* cannot work on a NULL PTE.
*/

if (!rv->csr_satp)
return memory_ifetch(addr);

Expand All @@ -300,157 +309,126 @@ static uint32_t mmu_ifetch(riscv_t *rv, const uint32_t addr)
return memory_ifetch(ppn | offset);
}

static uint32_t mmu_read_w(riscv_t *rv, const uint32_t addr)
static uint32_t mmu_read_w(riscv_t *rv, const uint32_t vaddr)
{
if (!rv->csr_satp)
return memory_read_w(addr);
uint32_t addr = rv->io.mem_translate(rv, vaddr, R);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return 0;
if (need_handle_signal)
return 0;
#endif
pte = mmu_walk(rv, addr, &level);
}

{
get_ppn_and_offset();
const uint32_t addr = ppn | offset;
const vm_attr_t *attr = PRIV(rv);
if (addr < attr->mem->mem_size)
return memory_read_w(addr);
if (addr == vaddr || addr < PRIV(rv)->mem->mem_size)
return memory_read_w(addr);

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
MMIO_READ();
MMIO_READ();
#endif
}

__UNREACHABLE;
}

static uint16_t mmu_read_s(riscv_t *rv, const uint32_t addr)
static uint16_t mmu_read_s(riscv_t *rv, const uint32_t vaddr)
{
if (!rv->csr_satp)
return memory_read_s(addr);
uint32_t addr = rv->io.mem_translate(rv, vaddr, R);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return 0;
if (need_handle_signal)
return 0;
#endif
pte = mmu_walk(rv, addr, &level);
}

get_ppn_and_offset();
return memory_read_s(ppn | offset);
return memory_read_s(addr);
}

static uint8_t mmu_read_b(riscv_t *rv, const uint32_t addr)
static uint8_t mmu_read_b(riscv_t *rv, const uint32_t vaddr)
{
if (!rv->csr_satp)
return memory_read_b(addr);
uint32_t addr = rv->io.mem_translate(rv, vaddr, R);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return 0;
if (need_handle_signal)
return 0;
#endif
pte = mmu_walk(rv, addr, &level);
}

{
get_ppn_and_offset();
const uint32_t addr = ppn | offset;
const vm_attr_t *attr = PRIV(rv);
if (addr < attr->mem->mem_size)
return memory_read_b(addr);
if (addr == vaddr || addr < PRIV(rv)->mem->mem_size)
return memory_read_b(addr);

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
MMIO_READ();
MMIO_READ();
#endif
}

__UNREACHABLE;
}

static void mmu_write_w(riscv_t *rv, const uint32_t addr, const uint32_t val)
static void mmu_write_w(riscv_t *rv, const uint32_t vaddr, const uint32_t val)
{
if (!rv->csr_satp)
return memory_write_w(addr, (uint8_t *) &val);
uint32_t addr = rv->io.mem_translate(rv, vaddr, W);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return;
if (need_handle_signal)
return;
#endif
pte = mmu_walk(rv, addr, &level);
}

{
get_ppn_and_offset();
const uint32_t addr = ppn | offset;
const vm_attr_t *attr = PRIV(rv);
if (addr < attr->mem->mem_size) {
memory_write_w(addr, (uint8_t *) &val);
return;
}
if (addr == vaddr || addr < PRIV(rv)->mem->mem_size) {
memory_write_w(addr, (uint8_t *) &val);
return;
}

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
MMIO_WRITE();
MMIO_WRITE();
#endif
}
}

static void mmu_write_s(riscv_t *rv, const uint32_t addr, const uint16_t val)
static void mmu_write_s(riscv_t *rv, const uint32_t vaddr, const uint16_t val)
{
if (!rv->csr_satp)
uint32_t addr = rv->io.mem_translate(rv, vaddr, W);

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
if (need_handle_signal)
return;
#endif

if (addr == vaddr)
return memory_write_s(addr, (uint8_t *) &val);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
if (unlikely(!ok)) {
memory_write_s(addr, (uint8_t *) &val);
}

static void mmu_write_b(riscv_t *rv, const uint32_t vaddr, const uint8_t val)
{
uint32_t addr = rv->io.mem_translate(rv, vaddr, W);

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return;
if (need_handle_signal)
return;
#endif
pte = mmu_walk(rv, addr, &level);

if (addr == vaddr || addr < PRIV(rv)->mem->mem_size) {
memory_write_b(addr, (uint8_t *) &val);
return;
}

get_ppn_and_offset();
memory_write_s(ppn | offset, (uint8_t *) &val);
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
MMIO_WRITE();
#endif
}

static void mmu_write_b(riscv_t *rv, const uint32_t addr, const uint8_t val)
/*
* TODO: dTLB can be introduced here to
* cache the gVA to gPA tranlation.
*/
static uint32_t mmu_translate(riscv_t *rv, uint32_t vaddr, bool rw)
{
if (!rv->csr_satp)
return memory_write_b(addr, (uint8_t *) &val);
return memory_read_w(addr);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return;
return 0;
#endif
pte = mmu_walk(rv, addr, &level);
}
Expand All @@ -459,15 +437,36 @@ static void mmu_write_b(riscv_t *rv, const uint32_t addr, const uint8_t val)
get_ppn_and_offset();
const uint32_t addr = ppn | offset;
const vm_attr_t *attr = PRIV(rv);
if (addr < attr->mem->mem_size) {
memory_write_b(addr, (uint8_t *) &val);
return;
}
if (addr < attr->mem->mem_size)
return memory_read_w(addr);

#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
MMIO_WRITE();
MMIO_READ();
#endif
}

__UNREACHABLE;
}

static uint16_t mmu_read_s(riscv_t *rv, const uint32_t addr)
{
if (!rv->csr_satp)
return memory_read_s(addr);

uint32_t level;
pte_t *pte = mmu_walk(rv, addr, &level);
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
if (unlikely(!ok)) {
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
CHECK_PENDING_SIGNAL(rv, need_handle_signal);
if (need_handle_signal)
return 0;
#endif
pte = mmu_walk(rv, addr, &level);
}

get_ppn_and_offset();
return memory_read_s(ppn | offset);
}

riscv_io_t mmu_io = {
Expand All @@ -482,6 +481,9 @@ riscv_io_t mmu_io = {
.mem_write_s = mmu_write_s,
.mem_write_b = mmu_write_b,

/* VA2PA handler */
.mem_translate = mmu_translate,

/* system services or essential routines */
.on_ecall = ecall_handler,
.on_ebreak = ebreak_handler,
Expand Down

0 comments on commit 9ff0244

Please sign in to comment.