Appendix A: ARM64 additions for Project 2

This appendix covers what is new relative to Project 1’s ARM64 reference, which you should still keep at hand.

Instructions

InstructionMeaning
stp x0, x1, [sp, #16]Store pair: store two registers at sp+16 and sp+24. Halves the instruction count when saving a context.
ldp x0, x1, [x2, #16]Load pair: the inverse, loads a pair of registers.
eretException return. Sets pc from ELR_ELn and PSTATE from SPSR_ELn (for the current exception level n) in one step. Used both to drop an exception level and to resume a saved context.
msr DAIFSET, #2Mask (disable) interrupts. The immediate is a 4-bit selector over the PSTATE mask bits: 8 = Debug, 4 = SError, 2 = IRQ, 1 = FIQ. So #2 touches only the IRQ mask.
msr DAIFCLR, #2Unmask (enable) interrupts; same selector encoding.
wfi / wfeWait for interrupt / event: put the core in a low-power state until something happens. A good way to park a CPU core that has nothing to do.
svc #0Supervisor call: deliberately raise a synchronous exception. This is how system calls will work in Project 4; here it is useful for testing your exception vector table.
.balign N(An assembler directive, not an instruction.) Advance to the next address that is a multiple of N, padding as needed. Essential for laying out an exception vector table.

System registers

Remember that all of these are read with mrs and written with msr, never mov, and that most of them exist separately at each exception level.

RegisterMeaning
CurrentELBits 3:2 hold the current exception level.
ELR_EL1Exception Link Register: the address to resume at. Set by the hardware on an exception; set by you when faking one.
SPSR_EL1Saved Program Status Register: the PSTATE to resume with. Its low four bits select the exception level and stack pointer to return to (0b0101 = EL1h, 0b0000 = EL0t).
ESR_EL1Exception Syndrome Register: why the (synchronous) exception happened. Bits 31:26 are the exception class; the rest is class-specific detail.
FAR_EL1Fault Address Register: the address that caused a memory fault.
VBAR_EL1Vector Base Address Register: the address of the exception vector table. 2 KiB-aligned. You must set this.
SCTLR_EL1System Control Register: MMU, caches, endianness, alignment checking.
HCR_EL2Hypervisor Configuration Register. Bit 31 (RW) selects AArch64 (1) or AArch32 (0) for lower exception levels.
DAIFThe four PSTATE interrupt-mask bits: Debug, SError (Abort), IRQ, FIQ. Read or write all four at once with mrs/msr DAIF (which is what you want in order to save and restore an interrupt state); set or clear individual ones with msr DAIFSET/DAIFCLR and the 4-bit selector above. Note that the bit positions in the DAIF register view, in the DAIFSET/DAIFCLR selector, and in SPSR_EL1 are all different: use the named constants in kernel/aarch64.h rather than writing numbers.
CNTFRQ_EL0Generic timer tick rate, in Hz (lower 32 bits).
CNTPCT_EL0Generic timer current count (64-bit, monotonic).
CNTP_CTL_EL0Generic timer control: bit 0 ENABLE, bit 1 IMASK, bit 2 ISTATUS (read-only).
CNTP_TVAL_EL0Generic timer down-counter: write N to fire N ticks from now.
MPIDR_EL1Multiprocessor Affinity Register: which core am I. (From Project 1.)

Reaching system registers from C

You cannot write msr in C, so you use GCC’s inline assembly:

// read a system register
uint64 freq;
__asm__ volatile("mrs %0, CNTFRQ_EL0" : "=r"(freq));

// write a system register
__asm__ volatile("msr CNTP_TVAL_EL0, %0" : : "r"(ticks));

// an instruction with no operands
__asm__ volatile("msr DAIFSET, #2" : : : "memory");

%0 refers to the first operand; "=r" means “an output in a general register”, "r" means “an input in a general register”. The "memory" clobber tells the compiler not to move memory accesses across the instruction, which matters whenever you are changing something (like the interrupt mask) that affects when other code can run. volatile stops the compiler from deleting the statement because it appears to have no effect.