17
17
use std:: sync:: { Arc , Mutex } ;
18
18
19
19
use cpuid_utils:: { CpuidIdent , CpuidSet , CpuidValues } ;
20
- use overlay:: {
21
- OverlayContents , OverlayError , OverlayKind , OverlayManager , OverlayPage ,
22
- } ;
20
+ use overlay:: { OverlayError , OverlayKind , OverlayManager , OverlayPage } ;
23
21
24
22
use crate :: {
25
23
accessors:: MemAccessor ,
26
24
common:: { Lifecycle , VcpuId } ,
27
25
enlightenment:: {
28
- hyperv:: {
29
- bits:: * ,
30
- hypercall:: { hypercall_page_contents, MsrHypercallValue } ,
31
- } ,
26
+ hyperv:: { bits:: * , hypercall:: MsrHypercallValue } ,
32
27
AddCpuidError ,
33
28
} ,
34
29
migrate:: {
@@ -52,6 +47,13 @@ mod probes {
52
47
53
48
const TYPE_NAME : & str = "guest-hyperv-interface" ;
54
49
50
+ /// A collection of overlay pages that a Hyper-V enlightenment stack might be
51
+ /// managing.
52
+ #[ derive( Default ) ]
53
+ struct OverlayPages {
54
+ hypercall : Option < OverlayPage > ,
55
+ }
56
+
55
57
#[ derive( Default ) ]
56
58
struct Inner {
57
59
/// This enlightenment's overlay manager.
@@ -63,7 +65,8 @@ struct Inner {
63
65
/// The last value stored in the [`bits::HV_X64_MSR_HYPERCALL`] MSR.
64
66
msr_hypercall_value : MsrHypercallValue ,
65
67
66
- hypercall_overlay : Option < OverlayPage > ,
68
+ /// This enlightenment's active overlay page handles.
69
+ overlays : OverlayPages ,
67
70
}
68
71
69
72
pub struct HyperV {
@@ -95,7 +98,7 @@ impl HyperV {
95
98
// manually cleared this bit).
96
99
if value == 0 {
97
100
inner. msr_hypercall_value . clear_enabled ( ) ;
98
- inner. hypercall_overlay . take ( ) ;
101
+ inner. overlays . hypercall . take ( ) ;
99
102
}
100
103
101
104
inner. msr_guest_os_id_value = value;
@@ -131,23 +134,22 @@ impl HyperV {
131
134
// the guest.
132
135
if !new. enabled ( ) {
133
136
inner. msr_hypercall_value = new;
134
- inner. hypercall_overlay . take ( ) ;
137
+ inner. overlays . hypercall . take ( ) ;
135
138
return WrmsrOutcome :: Handled ;
136
139
}
137
140
138
141
// Ensure the overlay is in the correct position.
139
- let res = if let Some ( overlay) = inner. hypercall_overlay . as_mut ( ) {
142
+ let res = if let Some ( overlay) = inner. overlays . hypercall . as_mut ( ) {
140
143
overlay. move_to ( new. gpfn ( ) )
141
144
} else {
142
145
inner
143
146
. overlay_manager
144
147
. add_overlay (
145
148
new. gpfn ( ) ,
146
- OverlayKind :: Hypercall ,
147
- OverlayContents ( Box :: new ( hypercall_page_contents ( ) ) ) ,
149
+ OverlayKind :: HypercallReturnNotSupported ,
148
150
)
149
151
. map ( |overlay| {
150
- inner. hypercall_overlay = Some ( overlay) ;
152
+ inner. overlays . hypercall = Some ( overlay) ;
151
153
} )
152
154
} ;
153
155
@@ -250,40 +252,63 @@ impl Lifecycle for HyperV {
250
252
TYPE_NAME
251
253
}
252
254
253
- fn reset ( & self ) {
255
+ fn pause ( & self ) {
254
256
let mut inner = self . inner . lock ( ) . unwrap ( ) ;
255
257
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
- } ;
258
+ // Remove all active overlays from service. If the VM migrates, this
259
+ // allows the original guest pages that sit underneath those overlays to
260
+ // be transferred as part of the guest RAM transfer phase instead of
261
+ // possibly being serialized and sent during the device state phase. Any
262
+ // active overlays will be re-established on the target during its
263
+ // device state import phase.
264
+ //
265
+ // Any guest data written to the overlay pages will be lost. That's OK
266
+ // because all the overlays this module currently supports are
267
+ // semantically read-only (guests should expect to take an exception if
268
+ // they try to write to them, although today no such exception is
269
+ // raised).
270
+ //
271
+ // The caller who is coordinating the "pause VM" operation is required
272
+ // to ensure that devices are paused only if vCPUs are paused, so no
273
+ // vCPU will be able to observe the missing overlay.
274
+ inner. overlays = OverlayPages :: default ( ) ;
271
275
272
- inner. overlay_manager . attach ( & self . acc_mem ) ;
276
+ assert ! ( inner. overlay_manager. is_empty ( ) ) ;
273
277
}
274
278
275
- fn halt ( & self ) {
279
+ fn resume ( & self ) {
276
280
let mut inner = self . inner . lock ( ) . unwrap ( ) ;
277
281
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
+ assert ! ( inner . overlay_manager . is_empty ( ) ) ;
283
+
284
+ // Re-establish any overlays that were removed when the enlightenment
285
+ // was paused .
282
286
//
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 ( ) ;
287
+ // Writes to the hypercall MSR only persist if they specify a valid
288
+ // overlay PFN, so adding the hypercall overlay is guaranteed to
289
+ // succeed.
290
+ let hypercall_overlay =
291
+ inner. msr_hypercall_value . enabled ( ) . then ( || {
292
+ inner
293
+ . overlay_manager
294
+ . add_overlay (
295
+ inner. msr_hypercall_value . gpfn ( ) ,
296
+ OverlayKind :: HypercallReturnNotSupported ,
297
+ )
298
+ . expect ( "hypercall MSR is only enabled with a valid PFN" )
299
+ } ) ;
300
+
301
+ inner. overlays = OverlayPages { hypercall : hypercall_overlay } ;
302
+ }
303
+
304
+ fn reset ( & self ) {
305
+ let inner = self . inner . lock ( ) . unwrap ( ) ;
306
+ assert ! ( inner. overlay_manager. is_empty( ) ) ;
307
+ }
308
+
309
+ fn halt ( & self ) {
310
+ let inner = self . inner . lock ( ) . unwrap ( ) ;
311
+ assert ! ( inner. overlay_manager. is_empty( ) ) ;
287
312
}
288
313
289
314
fn migrate ( & ' _ self ) -> Migrator < ' _ > {
@@ -309,58 +334,48 @@ impl MigrateSingle for HyperV {
309
334
mut offer : PayloadOffer ,
310
335
_ctx : & MigrateCtx ,
311
336
) -> Result < ( ) , MigrateStateError > {
312
- let data: migrate:: HyperVEnlightenmentV1 = offer. parse ( ) ?;
337
+ let migrate:: HyperVEnlightenmentV1 { msr_guest_os_id, msr_hypercall } =
338
+ offer. parse ( ) ?;
313
339
let mut inner = self . inner . lock ( ) . unwrap ( ) ;
314
340
341
+ // Re-establish any overlay pages that are active in the restored MSRs.
342
+ //
315
343
// A well-behaved source should ensure that the hypercall MSR value is
316
344
// within the guest's PA range and that its Enabled bit agrees with the
317
345
// value of the guest OS ID MSR. But this data was received over the
318
346
// wire, so for safety's sake, verify it all and return a migration
319
347
// error if anything is inconsistent.
320
- let hypercall_msr = MsrHypercallValue ( data . msr_hypercall ) ;
321
- if hypercall_msr . enabled ( ) {
322
- if data . msr_guest_os_id == 0 {
348
+ let msr_hypercall_value = MsrHypercallValue ( msr_hypercall) ;
349
+ let hypercall_overlay = if msr_hypercall_value . enabled ( ) {
350
+ if msr_guest_os_id == 0 {
323
351
return Err ( MigrateStateError :: ImportFailed (
324
352
"hypercall MSR enabled but guest OS ID MSR is 0"
325
353
. to_string ( ) ,
326
354
) ) ;
327
355
}
328
356
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
357
match inner. overlay_manager . add_overlay (
349
- hypercall_msr. gpfn ( ) ,
350
- OverlayKind :: Hypercall ,
351
- OverlayContents ( Box :: new ( hypercall_page_contents ( ) ) ) ,
358
+ msr_hypercall_value. gpfn ( ) ,
359
+ OverlayKind :: HypercallReturnNotSupported ,
352
360
) {
353
- Ok ( overlay) => inner . hypercall_overlay = Some ( overlay) ,
361
+ Ok ( overlay) => Some ( overlay) ,
354
362
Err ( e) => {
355
363
return Err ( MigrateStateError :: ImportFailed ( format ! (
356
364
"failed to re-establish hypercall overlay: {e}"
357
365
) ) )
358
366
}
359
367
}
360
- }
368
+ } else {
369
+ None
370
+ } ;
371
+
372
+ * inner = Inner {
373
+ overlay_manager : inner. overlay_manager . clone ( ) ,
374
+ msr_guest_os_id_value : msr_guest_os_id,
375
+ msr_hypercall_value,
376
+ overlays : OverlayPages { hypercall : hypercall_overlay } ,
377
+ } ;
361
378
362
- inner. msr_guest_os_id_value = data. msr_guest_os_id ;
363
- inner. msr_hypercall_value = hypercall_msr;
364
379
Ok ( ( ) )
365
380
}
366
381
}
0 commit comments