11// SPDX-License-Identifier: Apache-2.0
22// Copyright 2026 The Hyperlight Authors.
33use core:: fmt:: Write ;
4+ use core:: sync:: atomic:: { AtomicU64 , Ordering } ;
45
56use hyperlight_common:: arch:: exn:: { DataFault , DataFaultKind , Exception , decode_syndrome} ;
67use hyperlight_common:: vmem:: {
@@ -10,10 +11,50 @@ use hyperlight_guest::error::ErrorCode;
1011use hyperlight_guest:: exit:: write_abort;
1112use hyperlight_guest:: layout:: { MAIN_STACK_LIMIT_GVA , MAIN_STACK_TOP_GVA } ;
1213
13- use super :: super :: mrs;
14+ use super :: super :: { mrs, msr } ;
1415use super :: types:: * ;
1516use crate :: HyperlightAbortWriter ;
1617
18+ /// Callback for synchronous AArch64 exceptions not handled by Hyperlight.
19+ ///
20+ /// The arguments are the decoded exception, raw ESR_EL1, FAR_EL1, mutable
21+ /// ELR_EL1, and saved x0 through x30. Return `true` to resume execution.
22+ /// Call [`crate::paging::barrier::first_valid_same_ctx`] after mapping a page
23+ /// for a translation fault.
24+ pub type ExceptionHandler = fn ( Exception , u64 , u64 , & mut u64 , & mut [ u64 ; 31 ] ) -> bool ;
25+
26+ static HANDLER : AtomicU64 = AtomicU64 :: new ( 0 ) ;
27+
28+ /// Register the callback for synchronous AArch64 exceptions.
29+ ///
30+ /// A new callback replaces any existing callback.
31+ pub fn register_exception_handler ( handler : ExceptionHandler ) {
32+ HANDLER . store ( handler as usize as u64 , Ordering :: Release ) ;
33+ }
34+
35+ /// Remove the registered AArch64 exception callback.
36+ pub fn unregister_exception_handler ( ) {
37+ HANDLER . store ( 0 , Ordering :: Release ) ;
38+ }
39+
40+ fn try_handle_registered_exception (
41+ exn : Exception ,
42+ esr : u64 ,
43+ far : u64 ,
44+ elr : & mut u64 ,
45+ registers : & mut [ u64 ; 31 ] ,
46+ ) -> bool {
47+ let handler = HANDLER . load ( Ordering :: Acquire ) ;
48+ if handler == 0 {
49+ return false ;
50+ }
51+
52+ // SAFETY: HANDLER contains only function pointers stored by
53+ // register_exception_handler.
54+ let handler = unsafe { core:: mem:: transmute :: < u64 , ExceptionHandler > ( handler) } ;
55+ handler ( exn, esr, far, elr, registers)
56+ }
57+
1758fn handle_stack_fault ( far : u64 ) {
1859 // TODO: perhaps we should have a sanity check that the
1960 // stack grows only one page at a time, which should be
@@ -124,7 +165,7 @@ fn handle_internal_fault(exn: Exception, far: u64) -> bool {
124165pub ( super ) extern "C" fn handle_exception (
125166 typ : ExceptionType ,
126167 from : ExceptionFrom ,
127- _regs : * mut ExceptionContext ,
168+ regs : * mut ExceptionContext ,
128169) {
129170 let esr = unsafe { mrs ! ( ESR_EL1 ) } ;
130171 let far = unsafe { mrs ! ( FAR_EL1 ) } ;
@@ -134,6 +175,17 @@ pub(super) extern "C" fn handle_exception(
134175 if handle_internal_fault ( exn, far) {
135176 return ;
136177 }
178+
179+ let mut elr = unsafe { mrs ! ( ELR_EL1 ) } ;
180+ // SAFETY: Exception entry passes its live, uniquely borrowed save area.
181+ let registers = unsafe { & mut ( * regs) . x } ;
182+ if try_handle_registered_exception ( exn, esr, far, & mut elr, registers) {
183+ // SAFETY: The callback supplies the resume address for eret.
184+ unsafe {
185+ msr ! ( ELR_EL1 , elr) ;
186+ }
187+ return ;
188+ }
137189 }
138190
139191 // Die with some diagnostic information
0 commit comments