diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 51e5c3c2a932..5ec2c5c6b05e 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -368,6 +368,7 @@
- [Using It](bare-metal/aps/logging/using.md)
- [Exceptions](bare-metal/aps/exceptions.md)
- [aarch64-rt](bare-metal/aps/aarch64-rt.md)
+ - [Exceptions](bare-metal/aps/aarch64-rt/exceptions.md)
- [Other Projects](bare-metal/aps/other-projects.md)
- [Useful Crates](bare-metal/useful-crates.md)
- [`zerocopy`](bare-metal/useful-crates/zerocopy.md)
diff --git a/src/bare-metal/aps/aarch64-rt/exceptions.md b/src/bare-metal/aps/aarch64-rt/exceptions.md
new file mode 100644
index 000000000000..8af2eb752667
--- /dev/null
+++ b/src/bare-metal/aps/aarch64-rt/exceptions.md
@@ -0,0 +1,25 @@
+# Exceptions
+
+`aarch64-rt` provides a trait to define exception handlers, and a macro to
+generate the assembly code for the exception vector to call them.
+
+The trait has default implementations for each method which simply panic, so we
+can omit methods for exceptions we don't expect to happen.
+
+
+
+```rust,editable,compile_fail
+{{#include ../examples/src/exceptions_rt.rs:exceptions}}
+```
+
+
+
+- The `exception_handlers` macro generates a `global_asm!` block with the
+ exception vector to call into the Rust code, similar to the `exceptions.S` we
+ had before.
+- `RegisterStateRef` wraps a reference to the stack frame where the register
+ values were saved by the assembly code when the exception happed. This can be
+ used for example to extract the parameters for an SMC or HVC call from a lower
+ EL, and update the values to be restored when the exception handler returns.
+
+
diff --git a/src/bare-metal/aps/examples/Cargo.lock b/src/bare-metal/aps/examples/Cargo.lock
index fa4428d765e0..c60977ff86cb 100644
--- a/src/bare-metal/aps/examples/Cargo.lock
+++ b/src/bare-metal/aps/examples/Cargo.lock
@@ -14,9 +14,9 @@ dependencies = [
[[package]]
name = "aarch64-rt"
-version = "0.3.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21dc662bf8045ff4a57989d927607b71654b902f0a1976ec3e7fbb30a1477aa"
+checksum = "4969245b57b2aef286e6f507cd3fd1b9be7c6df1f0341141ab42226e3e6c52ae"
dependencies = [
"smccc",
]
diff --git a/src/bare-metal/aps/examples/Cargo.toml b/src/bare-metal/aps/examples/Cargo.toml
index 10a53ffd200a..34464650a24d 100644
--- a/src/bare-metal/aps/examples/Cargo.toml
+++ b/src/bare-metal/aps/examples/Cargo.toml
@@ -8,7 +8,7 @@ publish = false
[dependencies]
aarch64-paging = { version = "0.11.0", default-features = false }
-aarch64-rt = "0.3.1"
+aarch64-rt = "0.4.2"
arm-pl011-uart = "0.4.0"
bitflags = "2.10.0"
log = "0.4.29"
diff --git a/src/bare-metal/aps/examples/Makefile b/src/bare-metal/aps/examples/Makefile
index 8f91d594836a..74390b616428 100644
--- a/src/bare-metal/aps/examples/Makefile
+++ b/src/bare-metal/aps/examples/Makefile
@@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-.PHONY: build qemu qemu_logger qemu_minimal qemu_psci
+.PHONY: build qemu qemu_logger qemu_minimal qemu_psci qemu_rt qemu_safemmio
-all: minimal.bin improved.bin logger.bin
+all: improved.bin logger.bin minimal.bin psci.bin rt.bin safemmio.bin
build:
cargo build
diff --git a/src/bare-metal/aps/examples/src/exceptions.S b/src/bare-metal/aps/examples/src/exceptions.S
index bd86eebbdfc7..59122a0fdb1e 100644
--- a/src/bare-metal/aps/examples/src/exceptions.S
+++ b/src/bare-metal/aps/examples/src/exceptions.S
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+// ANCHOR: exceptions
/**
* Saves the volatile registers onto the stack. This currently takes
* 14 instructions, so it can be used in exception handlers with 18
@@ -77,39 +78,18 @@
.endm
/**
- * This is a generic handler for exceptions taken at the current EL
- * while using SP0. It behaves similarly to the SPx case by first
- * switching to SPx, doing the work, then switching back to SP0 before
- * returning.
+ * This is a generic handler for exceptions taken at the current EL. It saves
+ * volatile registers to the stack, calls the Rust handler, restores volatile
+ * registers, then returns.
*
- * Switching to SPx and calling the Rust handler takes 16
- * instructions. To restore and return we need an additional 16
- * instructions, so we can implement the whole handler within the
- * allotted 32 instructions.
+ * This also works for exceptions taken from lower ELs, if we don't care about
+ * non-volatile registers.
*
+ * Saving state and jumping to the Rust handler takes 15 instructions, and
+ * restoring and returning also takes 15 instructions, so we can fit the whole
+ * handler in 30 instructions, under the limit of 32.
*/
-.macro current_exception_sp0 handler:req
- msr spsel, #1
- save_volatile_to_stack
- bl \handler
- restore_volatile_from_stack
- msr spsel, #0
- eret
-.endm
-
-/**
- * This is a generic handler for exceptions taken at the current EL
- * while using SPx. It saves volatile registers, calls the Rust
- * handler, restores volatile registers, then returns.
- *
- * This also works for exceptions taken from EL0, if we don't care
- * about non-volatile registers.
- *
- * Saving state and jumping to the Rust handler takes 15 instructions,
- * and restoring and returning also takes 15 instructions, so we can
- * fit the whole handler in 30 instructions, under the limit of 32.
- */
-.macro current_exception_spx handler:req
+.macro current_exception handler:req
save_volatile_to_stack
bl \handler
restore_volatile_from_stack
@@ -121,64 +101,64 @@
.balign 0x800
vector_table_el1:
sync_cur_sp0:
- current_exception_sp0 sync_exception_current
+ current_exception sync_current
.balign 0x80
irq_cur_sp0:
- current_exception_sp0 irq_current
+ current_exception irq_current
.balign 0x80
fiq_cur_sp0:
- current_exception_sp0 fiq_current
+ current_exception fiq_current
.balign 0x80
serr_cur_sp0:
- current_exception_sp0 serr_current
+ current_exception serror_current
.balign 0x80
sync_cur_spx:
- current_exception_spx sync_exception_current
+ current_exception sync_current
.balign 0x80
irq_cur_spx:
- current_exception_spx irq_current
+ current_exception irq_current
.balign 0x80
fiq_cur_spx:
- current_exception_spx fiq_current
+ current_exception fiq_current
.balign 0x80
serr_cur_spx:
- current_exception_spx serr_current
+ current_exception serror_current
.balign 0x80
sync_lower_64:
- current_exception_spx sync_lower
+ current_exception sync_lower
.balign 0x80
irq_lower_64:
- current_exception_spx irq_lower
+ current_exception irq_lower
.balign 0x80
fiq_lower_64:
- current_exception_spx fiq_lower
+ current_exception fiq_lower
.balign 0x80
serr_lower_64:
- current_exception_spx serr_lower
+ current_exception serror_lower
.balign 0x80
sync_lower_32:
- current_exception_spx sync_lower
+ current_exception sync_lower
.balign 0x80
irq_lower_32:
- current_exception_spx irq_lower
+ current_exception irq_lower
.balign 0x80
fiq_lower_32:
- current_exception_spx fiq_lower
+ current_exception fiq_lower
.balign 0x80
serr_lower_32:
- current_exception_spx serr_lower
+ current_exception serror_lower
diff --git a/src/bare-metal/aps/examples/src/exceptions.rs b/src/bare-metal/aps/examples/src/exceptions.rs
index cc52a1fe3379..07b2185b78de 100644
--- a/src/bare-metal/aps/examples/src/exceptions.rs
+++ b/src/bare-metal/aps/examples/src/exceptions.rs
@@ -19,8 +19,8 @@ use smccc::psci::system_off;
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
-extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
- error!("sync_exception_current");
+extern "C" fn sync_current(_elr: u64, _spsr: u64) {
+ error!("sync_current");
system_off::().unwrap();
}
@@ -40,8 +40,8 @@ extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
-extern "C" fn serr_current(_elr: u64, _spsr: u64) {
- error!("serr_current");
+extern "C" fn serror_current(_elr: u64, _spsr: u64) {
+ error!("serror_current");
system_off::().unwrap();
}
@@ -68,7 +68,7 @@ extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
-extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
- error!("serr_lower");
+extern "C" fn serror_lower(_elr: u64, _spsr: u64) {
+ error!("serror_lower");
system_off::().unwrap();
}
diff --git a/src/bare-metal/aps/examples/src/exceptions_rt.rs b/src/bare-metal/aps/examples/src/exceptions_rt.rs
new file mode 100644
index 000000000000..aad0a8149f76
--- /dev/null
+++ b/src/bare-metal/aps/examples/src/exceptions_rt.rs
@@ -0,0 +1,45 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ANCHOR: exceptions
+use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers};
+use log::error;
+use smccc::Hvc;
+use smccc::psci::system_off;
+
+struct Handlers;
+
+impl ExceptionHandlers for Handlers {
+ extern "C" fn sync_current(_state: RegisterStateRef) {
+ error!("sync_current");
+ system_off::().unwrap();
+ }
+
+ extern "C" fn irq_current(_state: RegisterStateRef) {
+ error!("irq_current");
+ system_off::().unwrap();
+ }
+
+ extern "C" fn fiq_current(_state: RegisterStateRef) {
+ error!("fiq_current");
+ system_off::().unwrap();
+ }
+
+ extern "C" fn serror_current(_state: RegisterStateRef) {
+ error!("serror_current");
+ system_off::().unwrap();
+ }
+}
+
+exception_handlers!(Handlers);
diff --git a/src/bare-metal/aps/examples/src/main_rt.rs b/src/bare-metal/aps/examples/src/main_rt.rs
index e18129cdc23a..4d6a4375bef9 100644
--- a/src/bare-metal/aps/examples/src/main_rt.rs
+++ b/src/bare-metal/aps/examples/src/main_rt.rs
@@ -16,7 +16,7 @@
#![no_main]
#![no_std]
-mod exceptions;
+mod exceptions_rt;
use aarch64_paging::descriptor::Attributes;
use aarch64_rt::{InitialPagetable, entry, initial_pagetable};
diff --git a/src/bare-metal/aps/exceptions.md b/src/bare-metal/aps/exceptions.md
index 916d952fbc6b..719924c4c75f 100644
--- a/src/bare-metal/aps/exceptions.md
+++ b/src/bare-metal/aps/exceptions.md
@@ -26,6 +26,14 @@ calling into Rust code:
but not `Sync`, then we'll need to wrap it in something like a `Mutex` and put
it in a static.
+The assembly code for the exception vector:
+
+
+
+```armasm
+{{#include examples/src/exceptions.S:exceptions}}
+```
+
[1]: ../../concurrency/send-sync.md
diff --git a/src/exercises/bare-metal/rtc/src/exceptions.rs b/src/exercises/bare-metal/rtc/src/exceptions.rs
index 6c8132fdf4ff..ac5c99e07f49 100644
--- a/src/exercises/bare-metal/rtc/src/exceptions.rs
+++ b/src/exercises/bare-metal/rtc/src/exceptions.rs
@@ -22,7 +22,7 @@ struct Handlers;
impl ExceptionHandlers for Handlers {
extern "C" fn sync_current(_state: RegisterStateRef) {
- error!("sync_exception_current");
+ error!("sync_current");
system_off::().unwrap();
}
@@ -40,7 +40,7 @@ impl ExceptionHandlers for Handlers {
}
extern "C" fn serror_current(_state: RegisterStateRef) {
- error!("serr_current");
+ error!("serror_current");
system_off::().unwrap();
}
@@ -60,7 +60,7 @@ impl ExceptionHandlers for Handlers {
}
extern "C" fn serror_lower(_state: RegisterStateRef) {
- error!("serr_lower");
+ error!("serror_lower");
system_off::().unwrap();
}
}