Neep Help with Assembly

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.

IMM           rs1  funct3  rd    opcode
001000000000  00010  001  00110  0111100

my inline assembly

 let stack_ptr: usize = 0x80001000;  
 unsafe{
        asm!("lui ra, 0",
             "mv sp, {0}",
             "mret",
             in(reg) stack_ptr);
    }

Can anyone spot the error?
Here is the example in godbolt

I have also tried mv ra, zero instead of lui ra, 0
EDIT
I don’t get a trap unless I use mret

mret does not return to the address in ra, it returns to the address in the mepc CSR.

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.

Okay, while screwing around with my board, now I can’t upload any programs to it

Reading all registers
Reading 64 bytes @ address 0x08002000
Reading 64 bytes @ address 0x08001FC0
Received monitor command: reset halt
Expected an decimal digit (0-9)
Downloading 15616 bytes @ address 0x20010000
Downloading 6236 bytes @ address 0x20013D00
Downloading 3780 bytes @ address 0x20015560
ERROR: Verification of RAMCode failed @ address 0x80000000.
Write: 0x00100073 2A80006F
Read: 0x00000000 00000000
Failed to prepare for programming.
Failed to download RAMCode!
Error while determining flash info (Bank @ 0x20000000)
Writing register (pc = 0x20010000)
Starting target CPU...

I’m just completely confused about what it is that you are doing.

Is this code in initial setup, before any user mode code is ever run? Or is it run in a trap handler?

What are you running it on? Does the hardware even have user mode?

I have no idea where this comes from:

IMM           rs1  funct3  rd    opcode
001000000000  00010  001  00110  0111100

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.

Running it together and disassembling it:

bruce@rip:~$ echo '.word 0x2001133c' >foo.s
bruce@rip:~$ riscv64-unknown-elf-gcc foo.s -c -march=rv32gc -mabi=ilp32
bruce@rip:~$ riscv64-unknown-elf-objdump -d foo.o

foo.o:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <.text>:
   0:	133c                	addi	a5,sp,424
   2:	2001                	jal	0x2

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.

Here is my program running on HiFive1 Rev B. It won’t let me attach the elf file on the forum.

Results of this program

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);

:sweat_smile: 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.

my mistake, it is not “la t0, {0}” but “lw t0, {0}” :wink:

That didn’t help with the compiling, still getting undefined symbol a0. I’m sure this is a Rust issue, which I have posted on their forum.

My original assembly expands to this

foo:
        lui     a0, 524292
        addi    a0, a0, -1152
        mv      ra, zero
        add     sp, zero, a0
        mret    

        j       .LBB0_1
.LBB0_1:
        ret

Your assembly expands to this

foo:
        lui     a0, 524292
        addi    a0, a0, -1152
        #APP
        mv      ra, zero
.Lpcrel_hi0:
        auipc   t0, %pcrel_hi(a0)
        lw      t0, %pcrel_lo(.Lpcrel_hi0)(t0)
        lw      sp, 0(t0)
        mret    

        #NO_APP
        j       .LBB0_1
.LBB0_1:
        ret

both use a0, but the latter code will not compile

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. :man_shrugging:

Hi, thanks for the code.

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 :slight_smile: [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.

A couple more points:

  • 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.

1 Like

I got it! :slight_smile: 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.

Thanks for the help @bruce and @emmanuel

Great!