I’m using this bit of inline assembly in my rust code to enter user mode. The program traps at this point with mcause of 0x1 which is instruction access fault. I read MTVAL, but I’m not sure it is from the trap or just a default value.
I already setup the MEPC register prior to this. I use the Rust crate riscv which offers pre-compiled assembly for reading and programming CSRs. It works as I programmed the MTVEC register with the crate and my program traps into my handling function perfectly. I set MTVEC to my handler function address, then set MEPC to the user mode funtction address, set MPP to 0, then I implement the inline assembly. The riscv crate does not have any functions for setting the ra or sp registers and according to the Freedom Metal user mode example, they set the ra register to 0, and sp to a stack address for user mode purposes. However, when I complete the setup with mret I get an exception trap, Instruction Access Fault in MCAUSE.
It doesn’t make sense as an opcode with last two bits 00 is a 16 bit instruction, but something appears to be trying to interpret it as an I-format 32 bit instruction.
That’s a pretty big stack frame if something needs to add 424 to it. And 0x2001 is also a 16 bit instruction, an unconditional jump-and-link to itself in rv32 or an addiw with illegal destination register x0 in rv64.
Describing what you’re doing in English prose doesn’t help much as 90% of programming errors are the code not saying what you think it’s saying.
I can’t look at your rust code as I’m getting “bad gateway” from nginx.
In any case most of us here don’t know or use Rust, so you’d be better off posting your linked binary in elf or even hex form somewhere (but better elf).
behaviour
Or get everything working in straight asm or asm and C before you try to make it work with a compiler that we really don’t know the behavior of.
It seems I misunderstood the mtval contents. It is an address not machine code. The MTVAL contents comes out to 0x20011528 which is the address of my user function.
I do apologies, I am not an expert with rust, but I suspect that the issue came from your assembly code where I believe you are setting the sp to the address of the stack_ptr instead of the value (that i beleive is 0x80003b80). Could you try to replace your current assembly by
asm!(“mv ra, zero”,
“la t0, {0}”,
“lw sp, 0(t0)”,
“mret”,
in(reg) stack_ptr);
Neither am I. Your assembly compiles fine on godbolt, but it fails to link in rust. It says undefined symbol a0. This is probably a rust issue. I will keep you posted once I can actually try your suggestion. Appreciate your time.
I tried out the assembly in an online interpreter and it looks like my original assembly is working. The stack pointer’s value is the address of the data on the stack. I’ve looked at my programming and double checked the pointer value a thousand times. Unless I am misunderstanding something, it should be working.
I take the address of my user stack array, add the size of the array to get the top of the array as a pointer. Then set that to sp, it shouldn’t be any more complicated than that?
The more I look at this, I don’t think the assembly is the problem. What causes Instruction Access Faults?
you are right, your assembly is what you are expected. you mepc look correct, ra is supposed to be ignored when you do a mret. so according to your user_mode function, you will perform an ecall ( it means to have an execption mcause should be equal to 8. Does it is possible that {sprintln!("Exception Reason: {:?}",exception) did not print the good message?
I believe that part is working as I tried an ecall from machine mode and it printed the correct label of MachineEnvCall. I’ve tried multiple lines of code in the user function from reading CSRs, printing over uart, or just assigning a value to a local variable. The same trap exception happens regardless. I will keep analyzing my code and hopefully I can spot something. I also thought about PMP but I read all of the pmpcfgs and they are all off.
I still think you would get a lot more useful help if you got this running in C first, as most of us don’t know Rust in detail (if at all).
A few comments, which may or may not be causing problems:
calling sprintln!() after setting mepc and mstatus is extremely dodgy there is no guarantee at all that they are preserved over such a long time period and probably many thousands of instructions executed. This should be immediately before the mret, with no function calls of any kind in between.
for sure you can’t call sprintln!() from the user_mode() function. It needs to run in machine mode. If you want to do I/O from user mode then you need to use ECALL for that. That’s kind of the point of user mode.
I’m also not positive that it’s safe to call sptinln!() inside trap_handler(). Maybe.
it’s also a very bad idea to read mtval after calling other functions such as sprintln!() in trap_handler. Any registers you want to read should be done immediately on entry.
you seem to be simply putting the address of trap_hander() into mtvec. But as far as I can tell Rust has no way to know that it is a trap handler function and generate code to save the necessary registers (a0…a7 and t0…t6) before overwriting them. Normal functions are allowed to do whatever they want with those registers, but trap handlers must preserve them. You’re not doing anything manually to preserve them. So if you ever return from the trap handler the code that entered the trap will be utterly screwed (technical term).
This project is my senior design project, and one of my specifications is using Rust [smack] Using RISC-V and Rust has been a challenge as not a lot of rust people know riscv and not a lot of riscv people know rust. [/forehead]
Yeah I figured that, I plan on using ecall for something like that, but I’m at my wits end and i’m trying anything and everything possbile as I have to get
everything working by end of October. Once I get user mode working, I still have to configure PMP correctly and implement a secure bootloader. YIkes!
It has worked every single time, besides doesn’t the trap just set the program counter to mtvec?
I haven’t had any issues here, but it seems to be a good idea to read immediately as you suggest to be on the safe side
Definately, if I try to return from the trap handler those registers will need to be pushed to the stack then popped back on return, I just haven’t gotten that far yet.
I very seriously suggest that when you don’t know how to do things you should do only one unknown thing at a time, and combine them together after you know that each part works individually. For sure this is what I do. It’s the approach of a professional. There is even a management term for it: “retiring technical risk”. Or just “proof of concept” if you prefer.
I would split Rust out of the equation for the moment, and make this mode switching and trap handling stuff work in pure assembly language, or in assembly language and C.
Doing that, and then rewriting in Rust will almost certainly take much less total time than trying to do everything at one time and having no idea which part is going wrong.
if you are on a CPU that has PMP then the initial configuration at reset is that Machine mode has access to all of memory, and User mode has access to nothing. Definitely you should check the documentation on that. From section 3.6.1 of the priv manual “If no PMP entry matches an
S-mode or U-mode access, but at least one PMP entry is implemented, the access fails.” Here “implemented” means that the CPU supports PMP, not that PMP has been configured.
I also really recommend that you debug this low level stuff in an emulator not on physical hardware. Spike works just the same as the hardware, implements Machine mode and User mode and PMP and allows you to single step the program, examine registers etc.
I got it! I talked to @alistair23 and he mentioned that I wasn’t setting up MIE and MPIE. I added that then I took your comment on PMP and setup pmpfcg0 as TOR RWX, and pmpaddr0 to 0x20400000 which is all of the flash I believe. And it worked, hallelujah!
Now to write a more meaningful user function that can run ECALL then the trap handler can save registers , process request, then restore registers, and MRET to jump back.