1414//! that intends to implement a Hyper-V-compatible interface:
1515//! https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/main/tlfs/Requirements%20for%20Implementing%20the%20Microsoft%20Hypervisor%20Interface.pdf
1616
17- use std:: sync:: Mutex ;
17+ use std:: sync:: { Arc , Mutex } ;
1818
1919use cpuid_utils:: { CpuidIdent , CpuidSet , CpuidValues } ;
20+ use overlay:: {
21+ OverlayContents , OverlayError , OverlayKind , OverlayManager , OverlayPage ,
22+ } ;
2023
2124use crate :: {
2225 accessors:: MemAccessor ,
23- common:: { GuestRegion , Lifecycle , VcpuId , PAGE_SIZE } ,
26+ common:: { Lifecycle , VcpuId } ,
2427 enlightenment:: {
2528 hyperv:: {
2629 bits:: * ,
@@ -33,11 +36,11 @@ use crate::{
3336 PayloadOutput ,
3437 } ,
3538 msr:: { MsrId , RdmsrOutcome , WrmsrOutcome } ,
36- vmm:: SubMapping ,
3739} ;
3840
3941mod bits;
4042mod hypercall;
43+ mod overlay;
4144
4245#[ usdt:: provider( provider = "propolis" ) ]
4346mod probes {
@@ -49,13 +52,18 @@ mod probes {
4952
5053const TYPE_NAME : & str = "guest-hyperv-interface" ;
5154
52- #[ derive( Debug , Default ) ]
55+ #[ derive( Default ) ]
5356struct Inner {
57+ /// This enlightenment's overlay manager.
58+ overlay_manager : Arc < OverlayManager > ,
59+
5460 /// The last value stored in the [`bits::HV_X64_MSR_GUEST_OS_ID`] MSR.
5561 msr_guest_os_id_value : u64 ,
5662
5763 /// The last value stored in the [`bits::HV_X64_MSR_HYPERCALL`] MSR.
5864 msr_hypercall_value : MsrHypercallValue ,
65+
66+ hypercall_overlay : Option < OverlayPage > ,
5967}
6068
6169pub struct HyperV {
@@ -87,6 +95,7 @@ impl HyperV {
8795 // manually cleared this bit).
8896 if value == 0 {
8997 inner. msr_hypercall_value . clear_enabled ( ) ;
98+ inner. hypercall_overlay . take ( ) ;
9099 }
91100
92101 inner. msr_guest_os_id_value = value;
@@ -122,36 +131,42 @@ impl HyperV {
122131 // the guest.
123132 if !new. enabled ( ) {
124133 inner. msr_hypercall_value = new;
134+ inner. hypercall_overlay . take ( ) ;
125135 return WrmsrOutcome :: Handled ;
126136 }
127137
128- let memctx = self
129- . acc_mem
130- . access ( )
131- . expect ( "guest memory is always accessible during wrmsr" ) ;
132-
133- let region = GuestRegion ( new. gpa ( ) , PAGE_SIZE ) ;
134-
135- // Mapping will fail if the requested GPA is out of the guest's physical
136- // address range. The TLFS specifies that this should raise #GP.
137- let Some ( mapping) = memctx. writable_region ( & region) else {
138- probes:: hyperv_wrmsr_hypercall_bad_gpa!( || new. gpa( ) . 0 ) ;
139- return WrmsrOutcome :: GpException ;
138+ // Ensure the overlay is in the correct position.
139+ let res = if let Some ( overlay) = inner. hypercall_overlay . as_mut ( ) {
140+ overlay. move_to ( new. gpfn ( ) )
141+ } else {
142+ inner
143+ . overlay_manager
144+ . add_overlay (
145+ new. gpfn ( ) ,
146+ OverlayKind :: Hypercall ,
147+ OverlayContents ( Box :: new ( hypercall_page_contents ( ) ) ) ,
148+ )
149+ . map ( |overlay| {
150+ inner. hypercall_overlay = Some ( overlay) ;
151+ } )
140152 } ;
141153
142- // Write the hypercall instruction sequence to the requested GPA.
143- //
144- // TODO: TLFS section 5.2.1 specifies that when an overlay is removed,
145- // "the underlying GPA page is 'uncovered', and an existing mapping
146- // becomes accessible to the guest." Empirically, at least some other
147- // Hv#1 implementations don't appear to follow this rule, and most
148- // common guest OSes don't rely on being able to disable or remove the
149- // hypercall page. Nevertheless, Propolis should eventually follow this
150- // rule.
151- write_overlay_page ( & mapping, & hypercall_page_contents ( ) ) ;
152-
153- inner. msr_hypercall_value = new;
154- WrmsrOutcome :: Handled
154+ match res {
155+ Ok ( ( ) ) => {
156+ inner. msr_hypercall_value = new;
157+ WrmsrOutcome :: Handled
158+ }
159+ Err ( OverlayError :: AddressInaccessible ( _) ) => {
160+ WrmsrOutcome :: GpException
161+ }
162+ // There should only ever be one hypercall overlay at a time, and
163+ // guest memory should be accessible in the context of a VM exit, so
164+ // (barring some other invariant being violated) adding an overlay
165+ // should always succeed if the target PFN is valid.
166+ Err ( e) => {
167+ panic ! ( "unexpected error establishing hypercall overlay: {e}" )
168+ }
169+ }
155170 }
156171}
157172
@@ -225,25 +240,50 @@ impl super::Enlightenment for HyperV {
225240
226241 fn attach ( & self , mem_acc : & MemAccessor ) {
227242 mem_acc. adopt ( & self . acc_mem , Some ( TYPE_NAME . to_owned ( ) ) ) ;
243+ let inner = self . inner . lock ( ) . unwrap ( ) ;
244+ inner. overlay_manager . attach ( & self . acc_mem ) ;
228245 }
229246}
230247
231- fn write_overlay_page ( mapping : & SubMapping < ' _ > , contents : & [ u8 ; PAGE_SIZE ] ) {
232- let written = mapping
233- . write_bytes ( contents)
234- . expect ( "overlay pages are always writable" ) ;
235-
236- assert_eq ! ( written, PAGE_SIZE , "overlay pages can be written completely" ) ;
237- }
238-
239248impl Lifecycle for HyperV {
240249 fn type_name ( & self ) -> & ' static str {
241250 TYPE_NAME
242251 }
243252
244253 fn reset ( & self ) {
245254 let mut inner = self . inner . lock ( ) . unwrap ( ) ;
246- * inner = Inner :: default ( ) ;
255+
256+ // Create a new overlay manager that tracks no pages. The old overlay
257+ // manager needs to be dropped before any of the overlay pages that
258+ // referenced it, so explicitly replace the existing manager with a new
259+ // one (and drop the old one) before default-initializing the rest of
260+ // the enlightenment's state.
261+ let new_overlay_manager = Arc :: new ( OverlayManager :: default ( ) ) ;
262+ let _ = std:: mem:: replace (
263+ & mut inner. overlay_manager ,
264+ new_overlay_manager. clone ( ) ,
265+ ) ;
266+
267+ * inner = Inner {
268+ overlay_manager : new_overlay_manager,
269+ ..Default :: default ( )
270+ } ;
271+
272+ inner. overlay_manager . attach ( & self . acc_mem ) ;
273+ }
274+
275+ fn halt ( & self ) {
276+ let mut inner = self . inner . lock ( ) . unwrap ( ) ;
277+
278+ // Create a new overlay manager and drop the reference to the old one.
279+ // This should be the only active reference to this manager, since all
280+ // overlay page operations happen during VM exits, and the vCPUs have
281+ // all quiesced by this point.
282+ //
283+ // This ensures that when this object is dropped, any overlay pages it
284+ // owns can be dropped safely.
285+ assert_eq ! ( Arc :: strong_count( & inner. overlay_manager) , 1 ) ;
286+ inner. overlay_manager = OverlayManager :: new ( ) ;
247287 }
248288
249289 fn migrate ( & ' _ self ) -> Migrator < ' _ > {
@@ -267,9 +307,10 @@ impl MigrateSingle for HyperV {
267307 fn import (
268308 & self ,
269309 mut offer : PayloadOffer ,
270- ctx : & MigrateCtx ,
310+ _ctx : & MigrateCtx ,
271311 ) -> Result < ( ) , MigrateStateError > {
272312 let data: migrate:: HyperVEnlightenmentV1 = offer. parse ( ) ?;
313+ let mut inner = self . inner . lock ( ) . unwrap ( ) ;
273314
274315 // A well-behaved source should ensure that the hypercall MSR value is
275316 // within the guest's PA range and that its Enabled bit agrees with the
@@ -285,20 +326,39 @@ impl MigrateSingle for HyperV {
285326 ) ) ;
286327 }
287328
288- let Some ( mapping) = ctx
289- . mem
290- . writable_region ( & GuestRegion ( hypercall_msr. gpa ( ) , PAGE_SIZE ) )
291- else {
292- return Err ( MigrateStateError :: ImportFailed ( format ! (
293- "couldn't map hypercall page for MSR value \
294- {hypercall_msr:?}"
295- ) ) ) ;
296- } ;
297-
298- write_overlay_page ( & mapping, & hypercall_page_contents ( ) ) ;
329+ // TODO(#850): Registering a new overlay with the overlay manager is
330+ // the only way to get an `OverlayPage` for this overlay. However,
331+ // the page's contents were already migrated in the RAM transfer
332+ // phase, so it's not actually necessary to create a *new* overlay
333+ // here; in fact, it would be incorrect to do so if the hypercall
334+ // target PFN had multiple overlays and a different one was active!
335+ // Fortunately, there's only one overlay type right now, so there's
336+ // no way for a page to have multiple overlays.
337+ //
338+ // (It would also be incorrect to rewrite this page if the guest
339+ // wrote data to it expecting to retrieve it later, but per the
340+ // TLFS, writes to the hypercall page should #GP anyway, so guests
341+ // ought not to expect too much here.)
342+ //
343+ // A better approach is to have the overlay manager export and
344+ // import its contents and, on import, return the set of overlay
345+ // page registrations that it imported. This layer can then check
346+ // that these registrations are consistent with its MSR values
347+ // before proceeding.
348+ match inner. overlay_manager . add_overlay (
349+ hypercall_msr. gpfn ( ) ,
350+ OverlayKind :: Hypercall ,
351+ OverlayContents ( Box :: new ( hypercall_page_contents ( ) ) ) ,
352+ ) {
353+ Ok ( overlay) => inner. hypercall_overlay = Some ( overlay) ,
354+ Err ( e) => {
355+ return Err ( MigrateStateError :: ImportFailed ( format ! (
356+ "failed to re-establish hypercall overlay: {e}"
357+ ) ) )
358+ }
359+ }
299360 }
300361
301- let mut inner = self . inner . lock ( ) . unwrap ( ) ;
302362 inner. msr_guest_os_id_value = data. msr_guest_os_id ;
303363 inner. msr_hypercall_value = hypercall_msr;
304364 Ok ( ( ) )
0 commit comments