Inline assembly
Sometimes we need to use assembly to do things that aren’t possible with Rust code. For example, to make an HVC (hypervisor call) to tell the firmware to power off the system:
#![no_main]
#![no_std]
use core::arch::asm;
use core::panic::PanicInfo;
mod exceptions;
const PSCI_SYSTEM_OFF: u32 = 0x84000008;
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(_x0: u64, _x1: u64, _x2: u64, _x3: u64) {
// SAFETY: this only uses the declared registers and doesn't do anything
// with memory.
unsafe {
asm!("hvc #0",
inout("w0") PSCI_SYSTEM_OFF => _,
inout("w1") 0 => _,
inout("w2") 0 => _,
inout("w3") 0 => _,
inout("w4") 0 => _,
inout("w5") 0 => _,
inout("w6") 0 => _,
inout("w7") 0 => _,
options(nomem, nostack)
);
}
loop {}
}
(If you actually want to do this, use the smccc crate which has wrappers for all these functions.)
- PSCI is the Arm Power State Coordination Interface, a standard set of functions to manage system and CPU power states, among other things. It is implemented by EL3 firmware and hypervisors on many systems.
- The
0 => _
syntax means initialise the register to 0 before running the inline assembly code, and ignore its contents afterwards. We need to useinout
rather thanin
because the call could potentially clobber the contents of the registers. - This
main
function needs to be#[unsafe(no_mangle)]
andextern "C"
because it is called from our entry point inentry.S
. _x0
–_x3
are the values of registersx0
–x3
, which are conventionally used by the bootloader to pass things like a pointer to the device tree. According to the standard aarch64 calling convention (which is whatextern "C"
specifies to use), registersx0
–x7
are used for the first 8 arguments passed to a function, soentry.S
doesn’t need to do anything special except make sure it doesn’t change these registers.- Run the example in QEMU with
make qemu_psci
undersrc/bare-metal/aps/examples
.