-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preliminary support for MMU emulation #438
Conversation
This PR is not fully ready to be merged since testing is not yet fully designed. PR earlier to get some feedbacks for further design. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmarks
Benchmark suite | Current: 6317605 | Previous: 79302e2 | Ratio |
---|---|---|---|
Dhrystone |
1422 Average DMIPS over 10 runs |
1610 Average DMIPS over 10 runs |
1.13 |
Coremark |
1431.449 Average iterations/sec over 10 runs |
1414.587 Average iterations/sec over 10 runs |
0.99 |
This comment was automatically generated by workflow using github-action-benchmark.
The initial design mentioned in here does not fully consider the CSR such as satp CSR needs to be accessed during MMU translation. During implementation, the interface shall be changed to adapt MMU translation. |
How can we test the MMU specific operations? |
The testing idea can be break down to following steps:
If I am at the wrong path, please correct me. It take times to design this testing. So, I would try to support other peripherals emulation at the same time such as PLIC. |
The above sound great. I expect the lean and reasonably straightforward approach as following: |
sret instruction is used for returning from a trap when trap occurs in S-mode level. Thus, the execution flow will not be sequential. During basic block translation, the sret instruction should be considered as can_branch instruction. Moreover, the existing system instruction decoder does not support decoding the sret instruction. Thus, the ir->opcode should be set correctly to support decoding the sret instruction. The implementation of sret instruction is simply returning false for now, the improved implementation will be completed and tested in sysprog21#438.
sret instruction is used for returning from a trap when trap occurs in S-mode level. Thus, the execution flow will not be sequential. During basic block translation, the sret instruction should be considered as can_branch instruction. Moreover, the existing system instruction decoder does not support decoding the sret instruction. Thus, the ir->opcode should be set correctly to support decoding the sret instruction. The implementation of sret instruction is simply returning false for now, the improved implementation will be completed and tested in sysprog21#438 since the sret instruction involves privilege mode changing.
sret instruction is used for returning from a trap when trap occurs in S-mode level. Thus, the execution flow will not be sequential. During basic block translation, the sret instruction should be considered as can_branch instruction. Moreover, the existing system instruction decoder does not support decoding the sret instruction. Thus, the ir->opcode should be set correctly to support decoding the sret instruction. The implementation of sret instruction is simply returning false for now, the improved implementation will be completed and tested in sysprog21#438 since the sret instruction involves privilege mode changing.
During block emulation, I think the instructions are executed sequentially until block ends. As such, when a page fault exception is generated during block emulation, the RISC-V core has to jump to the corresponding exception handler. The potential problem is that even thought the PC could be updated in a exception handler, but the block to emulate is not updated, and this cause the page fault cannot be handled properly. I have tested calling |
Can you provide a minimal reproducible example so that @qwe661234 can verify if the current block chaining is functioning as expected? |
Steps to reproduce the VM test:
Some output would look like this:
Notice that the user space code starts at address "0x4" and the address "0x800000b0" is the supervisor exception handler entry. When instruction fetch fault occurs at address "0x4", the PC is updated to "0x800000b0" but the next instruction is still from address "0x4" and I think it should be from address "0x800000b0". The relevant information is as follows:
The consequent instruction address ( "0x8", "0xc", ... ) face the same problem. |
At first glance, it appears that the MMU was not set in |
Could you provide your |
Please ignore the "vm.c" file. The MMU setup is done in "vm_setup.c".
The expected flow for exception handling is that:
|
Sure. Here it is: |
Is the variable |
Agree. |
d784b88
to
b35f744
Compare
src/emulate.c
Outdated
@@ -103,7 +95,7 @@ static void trap_handler(riscv_t *rv); | |||
* identifier called tval, as both are handled by TRAP_HANDLER_IMPL. | |||
*/ | |||
#define TRAP_HANDLER_IMPL(type, code) \ | |||
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \ | |||
void rv_trap_##type(riscv_t *rv, uint32_t tval) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a pity that the helper functions can not be declared as static
. Can you find a way for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a pity that the helper functions can not be declared as
static
. Can you find a way for that?
We can register the trap_handler
with the I/O interface during the initialization of the RISC-V core, keeping the dispatch table concealed. When a page fault occurs, the trap_handler
is invoked, which in turn calls _trap_handler
, and then _trap_handler
calls __trap_handler
. This __trap_handler
may correspond to a helper function defined in emulate.c. The __trap_handler
would represent the original implementation of trap_handler, while _trap_handler
may need to inspect the scause
CSR to determine which TRAP_HANDLER_IMPL
to invoke. This approach ensures that the specific TRAP_HANDLER_IMPL
remains hidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In detail, trap_handler
callback (need to set corresponding scause CSR, in IO interface)
---(call)-----> _trap_handler
(check scause CSR) ---(dispatch)-----> TRAP_HANDLER_IMPL
(depends on scause CSR, static function in emulate.c)
---(call)-----> __trap_handler
(actual trap handler, static function in emulate.c)
Now, the trap handling flow is as following:
|
6547846
to
99acca3
Compare
Since Line 106 in 164da62
|
src/emulate.c
Outdated
#define TRAP_HANDLER_IMPL(type, code) \ | ||
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \ | ||
#define TRAP_HANDLER_IMPL(type) \ | ||
static void rv_trap_##type(riscv_t *rv) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not need different handler for different trap type anymore. The information for trapping is stored in m/scause
, m/stval
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not need different handler for different trap type anymore. The information for trapping is stored in
m/scause
,m/stval
.
Could you elaborate more on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more on this?
I meant we could simplify some handlers in _trap_handler()
(e.g. illegal_insn, breakpoint, misalligned). They have the same routine of reading m/stval
and m/scause
to identify the type of traps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more on this?
I meant we could simplify some handlers in
_trap_handler()
(e.g. illegal_insn, breakpoint, misalligned). They have the same routine of readingm/stval
andm/scause
to identify the type of traps.
It make sense.
ac9fa59
to
266e477
Compare
@vacantron, after merging this pull request, let's prepare the prebuilt MMU test executable file. |
Any reason to expose |
They should be
Do you mark them in system.c? |
Go ahead for avoid disclosing the internal functions. |
'system.h' is removed due to too less LoC, it is not worth to add a new header file. Thus, I moved the original content to 'riscv.h' instead. |
To boot a 32-bit RISC-V Linux with MMU, MMU emulation support is crucial, using the SV32 virtual memory scheme. This commit’s main changes include implementing the MMU-related riscv_io_t interface and binding it during RISC-V instance initialization. To enable reuse of riscv_io_t, its prototype is modified to accept the RISC-V core instance as the first parameter, allowing MMU-enabled I/O to access the SATP CSR. A trap_handler callback is also added to the riscv_io_t interface. This keeps the dispatch_table and _trap_handler static within emulate.c, aligning the schema with other handlers, like ebreak_handler and ecall_handler. With m/scause and m/stval set in SET_CAUSE_AND_TVAL_THEN_TRAP, _trap_handler can immediately dispatch without rechecking scause, avoiding the need for additional call of specific type of trap handler, thus no more need of TRAP_HANDLER_IMPL. For each memory access, the page table is walked to get the corresponding PTE. Depending on the PTE retrieval, several page faults may need handling. Thus, three exception handlers have been introduced: insn_pgfault, load_pgfault, and store_pgfault, used in MMU_CHECK_FAULT. This commit does not fully handle access faults since they are related to PMA and PMP, which may not be necessary for booting 32-bit RISC-V Linux (possibly supported in the future). Since Linux has not been booted yet, a test suite is needed to test the MMU emulation. This commit includes a test suite that implements a simple kernel space supervisor and a user space application. The supervisor prepares the page table and then passes control to the user space application to test the three aforementioned page faults. Related: sysprog21#310
Thank @ChinYikMing for contributing! |
Thanks all for reviewing. Ready to bring up Linux kernel on next PR! |
The purpose of this commit is to boot 32-bit RISC-V Linux in the future. The virtual memory scheme to support is Sv32. There are one change to original code base to adapt the MMU: the prototype of
riscv_io_t
interface needs to be changed. Particularly, add a RISC-V instance(riscv_t
) as the first parameter. MMU related callbacks require to access the satp CSR to perform a page table walk during virtual memory translation but satp CSR is stored in RISC-V instance(riscv_t
), thus it should have a way to access the satp CSR. The trivial solution is adding RISC-Vinstance(
riscv_t
) to the prototype ofriscv_io_t
interface.After this change, we can reuse riscv_io_t for system emulation afterward.
The rest of changes are implementing the Sv32 virtual memory scheme. For every memory access, it has to walk through the page table to get the corresponding PTE. Depends on the retrieval of PTE, there are several page faults to be handled if necessary, so there are three exceptions handlers have been introduced which are insn_pgfault, load_pgfault, and store_pgfault and they are used in MMU_CHECK_FAULT. In this commit, the access fault are not handled well since they are related to PMA and PMP and they might not the must to boot 32-bit RISC-V Linux (tested on semu). More PTE, S-mode, M-mode CSR helper macro are introduced as well.
Related: #310