@@ -14,27 +14,343 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17+ use std:: sync:: LazyLock ;
18+
19+ use hyperlight_common:: outb:: VmAction ;
20+ use mshv_bindings:: {
21+ hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA,
22+ hv_register_assoc, hv_register_name_HV_ARM64_REGISTER_CPACR_EL1,
23+ hv_register_name_HV_ARM64_REGISTER_FPCR, hv_register_name_HV_ARM64_REGISTER_FPSR,
24+ hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, hv_register_name_HV_ARM64_REGISTER_PC,
25+ hv_register_name_HV_ARM64_REGISTER_Q0, hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1,
26+ hv_register_name_HV_ARM64_REGISTER_SP_EL1, hv_register_name_HV_ARM64_REGISTER_TCR_EL1,
27+ hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1, hv_register_name_HV_ARM64_REGISTER_VBAR_EL1,
28+ hv_register_value, hv_u128, mshv_create_partition_v2, mshv_user_mem_region,
29+ } ;
30+ use mshv_ioctls:: { Mshv , VcpuFd , VmFd } ;
1731use tracing:: { Span , instrument} ;
1832
19- use crate :: hypervisor:: virtual_machine:: CreateVmError ;
33+ use crate :: hypervisor:: regs:: {
34+ CommonDebugRegs , CommonFpu , CommonRegisters , CommonSpecialRegisters ,
35+ } ;
36+ use crate :: hypervisor:: virtual_machine:: {
37+ CreateVmError , MapMemoryError , RegisterError , ResetVcpuError , RunVcpuError , UnmapMemoryError ,
38+ VirtualMachine , VmExit ,
39+ } ;
40+ use crate :: mem:: memory_region:: { MemoryRegion , MemoryRegionFlags } ;
41+
42+ static MSHV : LazyLock < std:: result:: Result < Mshv , CreateVmError > > =
43+ LazyLock :: new ( || Mshv :: new ( ) . map_err ( |e| CreateVmError :: HypervisorNotAvailable ( e. into ( ) ) ) ) ;
2044
21- /// Return `true` if the MSHV API is available
45+ /// Determine whether the MSHV hypervisor API is available on aarch64.
2246#[ instrument( skip_all, parent = Span :: current( ) , level = "Trace" ) ]
2347pub ( crate ) fn is_hypervisor_present ( ) -> bool {
24- // TODO(aarch64): implement MSHV detection
25- false
48+ match Mshv :: new ( ) {
49+ Ok ( _) => true ,
50+ Err ( _) => {
51+ tracing:: info!( "MSHV is not available on this system" ) ;
52+ false
53+ }
54+ }
2655}
2756
28- /// An MSHV implementation of a single-vcpu VM
57+ /// An MSHV implementation of a single-vcpu VM for aarch64.
2958#[ derive( Debug ) ]
3059pub ( crate ) struct MshvVm {
31- _placeholder : ( ) ,
60+ vm_fd : VmFd ,
61+ vcpu_fd : VcpuFd ,
3262}
3363
34- #[ allow( unused) ]
3564impl MshvVm {
36- #[ allow( unused) ]
65+ /// Create a new MSHV VM instance for aarch64.
66+ #[ instrument( skip_all, parent = Span :: current( ) , level = "Trace" ) ]
3767 pub ( crate ) fn new ( ) -> std:: result:: Result < Self , CreateVmError > {
38- unimplemented ! ( "MshvVm::new" )
68+ let mshv = MSHV . as_ref ( ) . map_err ( |e| e. clone ( ) ) ?;
69+
70+ let pr: mshv_create_partition_v2 = Default :: default ( ) ;
71+ let vm_fd = mshv
72+ . create_vm_with_args ( & pr)
73+ . map_err ( |e| CreateVmError :: CreateVmFd ( e. into ( ) ) ) ?;
74+
75+ vm_fd
76+ . initialize ( )
77+ . map_err ( |e| CreateVmError :: InitializeVm ( e. into ( ) ) ) ?;
78+
79+ let vcpu_fd = vm_fd
80+ . create_vcpu ( 0 )
81+ . map_err ( |e| CreateVmError :: CreateVcpuFd ( e. into ( ) ) ) ?;
82+
83+ Ok ( Self { vm_fd, vcpu_fd } )
84+ }
85+
86+ /// Helper to get a single 64-bit register by name.
87+ fn get_reg64 ( & self , name : u32 ) -> Result < u64 , RegisterError > {
88+ let mut reg = [ hv_register_assoc {
89+ name,
90+ ..Default :: default ( )
91+ } ] ;
92+ self . vcpu_fd
93+ . get_reg ( & mut reg)
94+ . map_err ( |e| RegisterError :: GetRegs ( e. into ( ) ) ) ?;
95+ Ok ( unsafe { reg[ 0 ] . value . reg64 } )
96+ }
97+
98+ /// Helper to set a single 64-bit register by name.
99+ fn set_reg64 ( & self , name : u32 , value : u64 ) -> Result < ( ) , RegisterError > {
100+ self . vcpu_fd
101+ . set_reg ( & [ hv_register_assoc {
102+ name,
103+ value : hv_register_value { reg64 : value } ,
104+ ..Default :: default ( )
105+ } ] )
106+ . map_err ( |e| RegisterError :: SetRegs ( e. into ( ) ) )
107+ }
108+
109+ /// Helper to get a single 128-bit register (Q/SIMD) by name.
110+ fn get_reg128 ( & self , name : u32 ) -> Result < u128 , RegisterError > {
111+ let mut reg = [ hv_register_assoc {
112+ name,
113+ ..Default :: default ( )
114+ } ] ;
115+ self . vcpu_fd
116+ . get_reg ( & mut reg)
117+ . map_err ( |e| RegisterError :: GetFpu ( e. into ( ) ) ) ?;
118+ let v = unsafe { reg[ 0 ] . value . reg128 } ;
119+ Ok ( ( v. high_part as u128 ) << 64 | v. low_part as u128 )
120+ }
121+
122+ /// Helper to set a single 128-bit register (Q/SIMD) by name.
123+ fn set_reg128 ( & self , name : u32 , value : u128 ) -> Result < ( ) , RegisterError > {
124+ self . vcpu_fd
125+ . set_reg ( & [ hv_register_assoc {
126+ name,
127+ value : hv_register_value {
128+ reg128 : hv_u128 {
129+ low_part : value as u64 ,
130+ high_part : ( value >> 64 ) as u64 ,
131+ } ,
132+ } ,
133+ ..Default :: default ( )
134+ } ] )
135+ . map_err ( |e| RegisterError :: SetFpu ( e. into ( ) ) )
136+ }
137+ }
138+
139+ impl VirtualMachine for MshvVm {
140+ unsafe fn map_memory (
141+ & mut self ,
142+ ( _slot, region) : ( u32 , & MemoryRegion ) ,
143+ ) -> std:: result:: Result < ( ) , MapMemoryError > {
144+ let mshv_region: mshv_user_mem_region = region. into ( ) ;
145+ self . vm_fd
146+ . map_user_memory ( mshv_region)
147+ . map_err ( |e| MapMemoryError :: Hypervisor ( e. into ( ) ) )
148+ }
149+
150+ fn unmap_memory (
151+ & mut self ,
152+ ( _slot, region) : ( u32 , & MemoryRegion ) ,
153+ ) -> std:: result:: Result < ( ) , UnmapMemoryError > {
154+ let mshv_region: mshv_user_mem_region = region. into ( ) ;
155+ self . vm_fd
156+ . unmap_user_memory ( mshv_region)
157+ . map_err ( |e| UnmapMemoryError :: Hypervisor ( e. into ( ) ) )
158+ }
159+
160+ fn run_vcpu (
161+ & mut self ,
162+ #[ cfg( feature = "trace_guest" ) ] _tc : & mut crate :: sandbox:: trace:: TraceContext ,
163+ ) -> std:: result:: Result < VmExit , RunVcpuError > {
164+ const UNMAPPED_GPA_MESSAGE : hv_message_type = hv_message_type_HVMSG_UNMAPPED_GPA;
165+ const INVALID_GPA_ACCESS_MESSAGE : hv_message_type = hv_message_type_HVMSG_GPA_INTERCEPT;
166+
167+ loop {
168+ let exit_reason = self . vcpu_fd . run ( ) ;
169+
170+ match exit_reason {
171+ Ok ( m) => {
172+ let msg_type = m. header . message_type ;
173+ match msg_type {
174+ UNMAPPED_GPA_MESSAGE | INVALID_GPA_ACCESS_MESSAGE => {
175+ let mem_msg = m
176+ . to_memory_info ( )
177+ . map_err ( |_| RunVcpuError :: DecodeIOMessage ( msg_type) ) ?;
178+ let gpa = mem_msg. guest_physical_address ;
179+
180+ // On aarch64, I/O is performed via MMIO writes to
181+ // the I/O page, similar to the KVM backend.
182+ let io_page_gpa =
183+ const { hyperlight_common:: layout:: io_page ( ) . unwrap ( ) . 0 } ;
184+ let access_type = mem_msg. header . intercept_access_type ;
185+ let is_write = access_type != 0 ;
186+
187+ if is_write
188+ && gpa >= io_page_gpa
189+ && ( gpa - io_page_gpa) < hyperlight_common:: vmem:: PAGE_SIZE as u64
190+ {
191+ let off = ( gpa - io_page_gpa) as usize ;
192+ let port = off / core:: mem:: size_of :: < u64 > ( ) ;
193+
194+ // Advance PC past the faulting instruction.
195+ // MSHV does not auto-advance PC on intercepts.
196+ let pc = mem_msg. header . pc ;
197+ let instruction_length = mem_msg. header . instruction_length as u64 ;
198+ self . vcpu_fd
199+ . set_reg ( & [ hv_register_assoc {
200+ name : hv_register_name_HV_ARM64_REGISTER_PC,
201+ value : hv_register_value {
202+ reg64 : pc + instruction_length,
203+ } ,
204+ ..Default :: default ( )
205+ } ] )
206+ . map_err ( |e| RunVcpuError :: IncrementRip ( e. into ( ) ) ) ?;
207+
208+ if port == VmAction :: Halt as usize {
209+ return Ok ( VmExit :: Halt ( ) ) ;
210+ } else {
211+ // The data value is read from the I/O page
212+ // by the host; pass the offset as context.
213+ return Ok ( VmExit :: IoOut (
214+ port as u16 ,
215+ ( off as u64 ) . to_le_bytes ( ) . to_vec ( ) ,
216+ ) ) ;
217+ }
218+ } else {
219+ // Non-I/O page memory access
220+ return match MemoryRegionFlags :: try_from ( mem_msg)
221+ . map_err ( |_| RunVcpuError :: ParseGpaAccessInfo )
222+ {
223+ Ok ( MemoryRegionFlags :: READ ) => Ok ( VmExit :: MmioRead ( gpa) ) ,
224+ Ok ( MemoryRegionFlags :: WRITE ) => Ok ( VmExit :: MmioWrite ( gpa) ) ,
225+ Ok ( _) => Ok ( VmExit :: Unknown ( "Unknown MMIO access" . to_string ( ) ) ) ,
226+ Err ( e) => Err ( e) ,
227+ } ;
228+ }
229+ }
230+ other => {
231+ return Ok ( VmExit :: Unknown ( format ! (
232+ "Unknown MSHV VCPU exit: {:?}" ,
233+ other
234+ ) ) ) ;
235+ }
236+ }
237+ }
238+ Err ( e) => match e. errno ( ) {
239+ libc:: EINTR => {
240+ return Ok ( VmExit :: Cancelled ( ) ) ;
241+ }
242+ libc:: EAGAIN => {
243+ return Ok ( VmExit :: Retry ( ) ) ;
244+ }
245+ _ => return Err ( RunVcpuError :: Unknown ( e. into ( ) ) ) ,
246+ } ,
247+ }
248+ }
249+ }
250+
251+ fn regs ( & self ) -> std:: result:: Result < CommonRegisters , RegisterError > {
252+ let mshv_regs = self
253+ . vcpu_fd
254+ . get_regs ( )
255+ . map_err ( |e| RegisterError :: GetRegs ( e. into ( ) ) ) ?;
256+ Ok ( CommonRegisters {
257+ x : mshv_regs. regs ,
258+ sp : mshv_regs. sp ,
259+ pc : mshv_regs. pc ,
260+ pstate : mshv_regs. pstate ,
261+ } )
262+ }
263+
264+ fn set_regs ( & self , regs : & CommonRegisters ) -> std:: result:: Result < ( ) , RegisterError > {
265+ use mshv_bindings:: StandardRegisters ;
266+ let mshv_regs = StandardRegisters {
267+ regs : regs. x ,
268+ sp : regs. sp ,
269+ pc : regs. pc ,
270+ pstate : regs. pstate ,
271+ // sp_el1 and elr_el1 are managed via special registers
272+ sp_el1 : 0 ,
273+ elr_el1 : 0 ,
274+ fpsr : 0 ,
275+ fpcr : 0 ,
276+ } ;
277+ self . vcpu_fd
278+ . set_regs ( & mshv_regs)
279+ . map_err ( |e| RegisterError :: SetRegs ( e. into ( ) ) )
280+ }
281+
282+ fn fpu ( & self ) -> std:: result:: Result < CommonFpu , RegisterError > {
283+ let mut v: [ u128 ; 32 ] = [ 0 ; 32 ] ;
284+ for i in 0 ..32u32 {
285+ v[ i as usize ] = self . get_reg128 ( hv_register_name_HV_ARM64_REGISTER_Q0 + i) ?;
286+ }
287+ let fpsr = self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_FPSR) ? as u32 ;
288+ let fpcr = self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_FPCR) ? as u32 ;
289+ Ok ( CommonFpu { v, fpsr, fpcr } )
290+ }
291+
292+ fn set_fpu ( & self , fpu : & CommonFpu ) -> std:: result:: Result < ( ) , RegisterError > {
293+ for i in 0 ..32u32 {
294+ self . set_reg128 ( hv_register_name_HV_ARM64_REGISTER_Q0 + i, fpu. v [ i as usize ] ) ?;
295+ }
296+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_FPSR, fpu. fpsr as u64 ) ?;
297+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_FPCR, fpu. fpcr as u64 ) ?;
298+ Ok ( ( ) )
299+ }
300+
301+ fn sregs ( & self ) -> std:: result:: Result < CommonSpecialRegisters , RegisterError > {
302+ Ok ( CommonSpecialRegisters {
303+ ttbr0_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1) ?,
304+ tcr_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_TCR_EL1) ?,
305+ mair_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_MAIR_EL1) ?,
306+ sctlr_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1) ?,
307+ cpacr_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_CPACR_EL1) ?,
308+ vbar_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_VBAR_EL1) ?,
309+ sp_el1 : self . get_reg64 ( hv_register_name_HV_ARM64_REGISTER_SP_EL1) ?,
310+ } )
311+ }
312+
313+ fn set_sregs ( & self , sregs : & CommonSpecialRegisters ) -> std:: result:: Result < ( ) , RegisterError > {
314+ self . set_reg64 (
315+ hv_register_name_HV_ARM64_REGISTER_TTBR0_EL1,
316+ sregs. ttbr0_el1 ,
317+ ) ?;
318+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_TCR_EL1, sregs. tcr_el1 ) ?;
319+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_MAIR_EL1, sregs. mair_el1 ) ?;
320+ self . set_reg64 (
321+ hv_register_name_HV_ARM64_REGISTER_SCTLR_EL1,
322+ sregs. sctlr_el1 ,
323+ ) ?;
324+ self . set_reg64 (
325+ hv_register_name_HV_ARM64_REGISTER_CPACR_EL1,
326+ sregs. cpacr_el1 ,
327+ ) ?;
328+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_VBAR_EL1, sregs. vbar_el1 ) ?;
329+ self . set_reg64 ( hv_register_name_HV_ARM64_REGISTER_SP_EL1, sregs. sp_el1 ) ?;
330+ Ok ( ( ) )
331+ }
332+
333+ fn debug_regs ( & self ) -> std:: result:: Result < CommonDebugRegs , RegisterError > {
334+ // Debug register support on aarch64 MSHV is not yet implemented
335+ Ok ( CommonDebugRegs :: default ( ) )
336+ }
337+
338+ fn set_debug_regs ( & self , _drs : & CommonDebugRegs ) -> std:: result:: Result < ( ) , RegisterError > {
339+ // Debug register support on aarch64 MSHV is not yet implemented
340+ Ok ( ( ) )
341+ }
342+
343+ fn can_reset_vcpu ( & self ) -> bool {
344+ true
345+ }
346+
347+ fn reset_vcpu ( & mut self ) -> Result < ( ) , ResetVcpuError > {
348+ // Reset the vCPU by zeroing all general-purpose registers
349+ use mshv_bindings:: StandardRegisters ;
350+ let regs = StandardRegisters :: default ( ) ;
351+ self . vcpu_fd
352+ . set_regs ( & regs)
353+ . map_err ( |e| ResetVcpuError :: Hypervisor ( e. into ( ) ) ) ?;
354+ Ok ( ( ) )
39355 }
40356}
0 commit comments