Introduction
Assembly
| Register | ABI Name (alias) | Description |
|---|---|---|
pc |
pc |
Program counter (where the next instruction is) |
x0 |
zero |
Hardwired zero (always reads as zero) |
x1 |
ra |
Return address |
x2 |
sp |
Stack pointer |
x5 - x7 |
t0 - t2 |
Temporary registers |
x8 |
fp |
Stack frame pointer |
x10 - x11 |
a0 - a1 |
Function arguments/return values |
x12 - x17 |
a2 - a7 |
Function arguments |
x18 - x27 |
s0 - s11 |
Temporary registers saved across calls |
x28 - x31 |
t3 - t6 |
Temporary registers |
ra - When you call a function using an instruction like jal, the address of the next instruction is stored is stored here so the function knows where to return when it returns. |
fp - It is used to keep track of the start of the current function’s frame stack.
t3-t6 - Caller-Saved
s2-s11 - Callee-Saved
Memory access
lw a0, (a1) // Read a word (32-bits) from address in a1
// and store it in a0. In C, this would be: a0 = *a1;
sw a0, (a1) // Store a word in a0 to the address in a1.
// In C, this would be: *a1 = a0;
Branch Instructions
bnez a0, <label> // Go to <label> if a0 is not zero
// If a0 is zero, continue here
<label>:
// If a0 is not zero, continue here
Other branching instructions - bnez, bne, beq, blt
Functions Calls
li a0, 123 // Load 123 to a0 register (function argument)
jal ra, <label> // Jump to <label> and store the return address
// in the ra register.
// After the function call, continue here...
// int func(int a) {
// a += 1;
// return a;
// }
<label>:
addi a0, a0, 1 // Increment a0 (first argument) by 1
ret // Return to the address stored in ra.
// a0 register has the return value.
jal (jump and link) is used to to go to a specific label and store the return value
Note - a0-a7 are used for function arguments and a0 is used for return value
Stack
LIFO memory space, grows downwards.
To save a value into stack, decrement the stack pointer and store the value (push operation)
addi sp, sp, -4 // Move the stack pointer down by 4 bytes
// (i.e. stack allocation).
sw a0, (sp) // Store a0 to the stack
To load a value from the stack, load the value and increment the stack pointer (pop operation)
lw a0, (sp)
addi sp, sp, 4
CPU Modes
The CPU has three modes
| M-mode | Mode in which OpenSBI (i.e. BIOS) operates. |
| S-mode | Mode in which the kernel operates, aka. "kernel mode". |
| U-mode | Mode in which applications operate, aka. "user mode". |
Privileged Instructions
| Opcode and operands | Overview | Pseudocode |
|---|---|---|
csrr rd, csr |
Read from CSR | rd = csr; |
csrw csr, rs |
Write to CSR | csr = rs; |
csrrw rd, csr, rs |
Read from and write to CSR at once | tmp = csr; csr = rs; rd = tmp; |
sret |
Return from trap handler (restoring program counter, operation mode, etc.) | |
sfence.vma |
Clear Translation Lookaside Buffer (TLB) | |
| CSR (Control and Status Register) is a register that stores CPU settings |
What doe SRET do?
1) Set the program counter to SEPC 2) Restoring the mode using SPP (Supervisor Previous Privilege) 3) Restoring interrupts (SIE and SPIE), if the interrupts were on before the trap, they are no turned back on 4) Clearing memory protection (MPRV - Modify Privilege) bit allows kernel to access memory as if it were a lower privilege
Inline assembly
uint32_t value;
__asm__ __volatile__ ("csrr %0, sepc": "=r"(value));
__asm__ __volatile__("assembly" : output operands : input operands : clobbered registers);
=r (register) for output operands, and r for input operands.
Output and input operands can be accessed in the assembly code using %0, %1, etc. in order of starting from the output operands.
__asm__ __volatile__("csrw sscratch, %0":"r"(123));