Skip to content

Commit c4e288d

Browse files
author
Sangho Lee
committed
support more than 64 VPs
1 parent 4e70ee8 commit c4e288d

File tree

8 files changed

+120
-78
lines changed

8 files changed

+120
-78
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

litebox_platform_lvbs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ spin = { version = "0.10.0", default-features = false, features = [
1717
"once",
1818
"rwlock",
1919
] }
20+
bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] }
2021
arrayvec = { version = "0.7.6", default-features = false }
2122
rangemap = { version = "1.5.1", features = ["const_fn"] }
2223
thiserror = { version = "2.0.6", default-features = false }

litebox_platform_lvbs/src/arch/x86/mm/paging.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ impl<M: MemoryProvider, const ALIGN: usize> X64PageTable<'_, M, ALIGN> {
484484
/// # Behavior
485485
/// - Any existing mapping is treated as an error
486486
/// - On error, all pages mapped by this call are unmapped (atomic)
487+
#[cfg(feature = "optee_syscall")]
487488
pub(crate) fn map_non_contiguous_phys_frames(
488489
&self,
489490
frames: &[PhysFrame<Size4KiB>],
@@ -551,6 +552,7 @@ impl<M: MemoryProvider, const ALIGN: usize> X64PageTable<'_, M, ALIGN> {
551552
///
552553
/// Note: The caller must already hold the page table lock (`self.inner`).
553554
/// This function accepts the locked `MappedPageTable` directly.
555+
#[cfg(feature = "optee_syscall")]
554556
fn rollback_mapped_pages(
555557
inner: &mut MappedPageTable<'_, FrameMapping<M>>,
556558
pages: x86_64::structures::paging::page::PageRangeInclusive<Size4KiB>,

litebox_platform_lvbs/src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,12 @@ use litebox::{
2424
shim::ContinueOperation,
2525
utils::TruncateExt,
2626
};
27-
use litebox_common_linux::{
28-
PunchthroughSyscall,
29-
errno::Errno,
30-
vmap::{
31-
PhysPageAddr, PhysPageAddrArray, PhysPageMapInfo, PhysPageMapPermissions, PhysPointerError,
32-
VmapManager,
33-
},
27+
#[cfg(feature = "optee_syscall")]
28+
use litebox_common_linux::vmap::{
29+
PhysPageAddr, PhysPageAddrArray, PhysPageMapInfo, PhysPageMapPermissions, PhysPointerError,
30+
VmapManager,
3431
};
32+
use litebox_common_linux::{PunchthroughSyscall, errno::Errno};
3533
use x86_64::{
3634
VirtAddr,
3735
structures::paging::{

litebox_platform_lvbs/src/mm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub trait MemoryProvider {
5050
fn pa_to_va(pa: PhysAddr) -> VirtAddr {
5151
let pa = pa.as_u64() & !Self::PRIVATE_PTE_MASK;
5252
let va = VirtAddr::new_truncate(pa + Self::GVA_OFFSET.as_u64());
53+
#[cfg(feature = "optee_syscall")]
5354
assert!(
5455
va.as_u64() < vmap::VMAP_START as u64,
5556
"VA {va:#x} is out of range for direct mapping"

litebox_platform_lvbs/src/mshv/hvcall_mm.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
66
#[cfg(not(test))]
77
use crate::mshv::{
8-
HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES, HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST,
9-
HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE, HvInputFlushVirtualAddressList,
10-
HvInputFlushVirtualAddressSpace, hvcall::hv_do_hypercall, vtl_switch::vtl1_vp_mask,
8+
HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES, HV_FLUSH_EX_VP_SET_BANKS, HV_GENERIC_SET_SPARSE_4K,
9+
HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX, HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX,
10+
HvInputFlushVirtualAddressListEx, HvInputFlushVirtualAddressSpaceEx, hvcall::hv_do_hypercall,
11+
vtl_switch::vtl1_vp_mask,
1112
};
1213
use crate::{
1314
host::per_cpu_variables::with_per_cpu_variables_mut,
@@ -19,6 +20,21 @@ use crate::{
1920
},
2021
};
2122

23+
#[cfg(not(test))]
24+
#[inline]
25+
fn vp_set_valid_bank_mask(vp_set_bank_contents: [u64; HV_FLUSH_EX_VP_SET_BANKS]) -> u64 {
26+
vp_set_bank_contents
27+
.iter()
28+
.enumerate()
29+
.fold(0u64, |mask, (bank, contents)| {
30+
if *contents != 0 {
31+
mask | (1u64 << bank)
32+
} else {
33+
mask
34+
}
35+
})
36+
}
37+
2238
/// Hyper-V Hypercall to prevent lower VTLs (i.e., VTL0) from accessing a specified range of
2339
/// guest physical memory pages with a given protection flag.
2440
pub fn hv_modify_vtl_protection_mask(
@@ -74,20 +90,27 @@ pub fn hv_modify_vtl_protection_mask(
7490
/// This is the cross-core equivalent of a local CR3 reload.
7591
#[cfg(not(test))]
7692
pub(crate) fn hv_flush_virtual_address_space() -> Result<(), HypervCallError> {
93+
let vp_mask = vtl1_vp_mask();
94+
let valid_bank_mask = vp_set_valid_bank_mask(vp_mask);
95+
if valid_bank_mask == 0 {
96+
return Ok(());
97+
}
7798
let input = with_per_cpu_variables_mut(|pcv| unsafe {
7899
&mut *pcv
79100
.hv_hypercall_input_page_as_mut_ptr()
80-
.cast::<HvInputFlushVirtualAddressSpace>()
101+
.cast::<HvInputFlushVirtualAddressSpaceEx>()
81102
});
82103

83-
*input = HvInputFlushVirtualAddressSpace {
104+
*input = HvInputFlushVirtualAddressSpaceEx {
84105
address_space: 0,
85106
flags: HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES,
86-
processor_mask: vtl1_vp_mask(),
107+
vp_set_format: HV_GENERIC_SET_SPARSE_4K,
108+
vp_set_valid_bank_mask: valid_bank_mask,
109+
vp_set_bank_contents: vp_mask,
87110
};
88111

89112
hv_do_hypercall(
90-
u64::from(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE),
113+
u64::from(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX),
91114
(&raw const *input).cast::<core::ffi::c_void>(),
92115
core::ptr::null_mut(),
93116
)?;
@@ -115,15 +138,22 @@ pub(crate) fn hv_flush_virtual_address_list(
115138
);
116139
debug_assert!(page_count > 0, "page_count must not be 0");
117140

141+
let vp_mask = vtl1_vp_mask();
142+
let valid_bank_mask = vp_set_valid_bank_mask(vp_mask);
143+
if valid_bank_mask == 0 {
144+
return Ok(());
145+
}
118146
let input = with_per_cpu_variables_mut(|pcv| unsafe {
119147
&mut *pcv
120148
.hv_hypercall_input_page_as_mut_ptr()
121-
.cast::<HvInputFlushVirtualAddressList>()
149+
.cast::<HvInputFlushVirtualAddressListEx>()
122150
});
123151

124152
input.address_space = 0;
125153
input.flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES;
126-
input.processor_mask = vtl1_vp_mask();
154+
input.vp_set_format = HV_GENERIC_SET_SPARSE_4K;
155+
input.vp_set_valid_bank_mask = valid_bank_mask;
156+
input.vp_set_bank_contents = vp_mask;
127157

128158
let mut remaining = page_count;
129159
let mut current_va = start_va;
@@ -132,7 +162,7 @@ pub(crate) fn hv_flush_virtual_address_list(
132162
let mut gva_count: u16 = 0;
133163

134164
while remaining > 0
135-
&& (gva_count as usize) < HvInputFlushVirtualAddressList::MAX_GVAS_PER_REQUEST
165+
&& (gva_count as usize) < HvInputFlushVirtualAddressListEx::MAX_GVAS_PER_REQUEST
136166
{
137167
// Each entry can cover up to `MAX_ADDITIONAL_PAGES + 1` pages.
138168
let additional = remaining.saturating_sub(1).min(MAX_ADDITIONAL_PAGES);
@@ -148,9 +178,9 @@ pub(crate) fn hv_flush_virtual_address_list(
148178
}
149179

150180
hv_do_rep_hypercall(
151-
HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST,
181+
HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX,
152182
gva_count,
153-
0, // simple processor mask, not HV_VP_SET
183+
HvInputFlushVirtualAddressListEx::VP_SET_QWORD_COUNT,
154184
(&raw const *input).cast::<core::ffi::c_void>(),
155185
core::ptr::null_mut(),
156186
)?;

litebox_platform_lvbs/src/mshv/mod.rs

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod vsm_intercept;
1515
pub mod vtl1_mem_layout;
1616
pub mod vtl_switch;
1717

18+
use crate::arch::MAX_CORES;
1819
use crate::mshv::vtl1_mem_layout::PAGE_SIZE;
1920
use modular_bitfield::prelude::*;
2021
use modular_bitfield::specifiers::{B3, B4, B7, B8, B16, B31, B32, B45, B51, B62};
@@ -73,8 +74,8 @@ pub const VTL_ENTRY_REASON_RESERVED: u32 = 0x0;
7374
pub const VTL_ENTRY_REASON_LOWER_VTL_CALL: u32 = 0x1;
7475
pub const VTL_ENTRY_REASON_INTERRUPT: u32 = 0x2;
7576

76-
pub const HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE: u16 = 0x_0002;
77-
pub const HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST: u16 = 0x_0003;
77+
pub const HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX: u16 = 0x_0013;
78+
pub const HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX: u16 = 0x_0014;
7879
pub const HVCALL_MODIFY_VTL_PROTECTION_MASK: u16 = 0x_000c;
7980
pub const HVCALL_ENABLE_VP_VTL: u16 = 0x_000f;
8081
pub const HVCALL_GET_VP_REGISTERS: u16 = 0x_0050;
@@ -525,62 +526,65 @@ impl Default for HvInputModifyVtlProtectionMask {
525526
}
526527
}
527528

528-
/// Input structure for `HvCallFlushVirtualAddressSpace` (0x0002).
529+
/// VP-set format for sparse 4K virtual processor numbering.
530+
pub const HV_GENERIC_SET_SPARSE_4K: u64 = 0;
531+
532+
/// Number of VP banks encoded in EX flush requests.
529533
///
530-
/// Layout (TLFS §6.5.2):
531-
/// - Fixed header: `address_space` (u64) + `flags` (u64) + `processor_mask` (u64)
534+
/// Each bank contains 64 VPs.
535+
pub const HV_FLUSH_EX_VP_SET_BANKS: usize = MAX_CORES.div_ceil(64);
536+
537+
/// Input structure for `HvCallFlushVirtualAddressSpaceEx` (0x0013).
532538
///
533-
/// This is the **non-extended** version; it uses a simple 64-bit processor
534-
/// mask (one bit per VP, max 64 VPs). When `HV_FLUSH_ALL_PROCESSORS` is
535-
/// set in `flags` the mask is ignored, but must still be present.
539+
/// Layout (TLFS §6.5.2 EX):
540+
/// - `address_space` (u64)
541+
/// - `flags` (u64)
542+
/// - VP set header: `vp_set_format` (u64), `vp_set_valid_bank_mask` (u64)
543+
/// - VP set banks: `vp_set_bank_contents` (u64 per bank)
536544
#[derive(Clone, Copy)]
537545
#[repr(C, packed)]
538-
pub struct HvInputFlushVirtualAddressSpace {
539-
/// CR3 / EPTP value identifying the address space. Ignored when
540-
/// `HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES` is set.
546+
pub struct HvInputFlushVirtualAddressSpaceEx {
541547
pub address_space: u64,
542-
/// Combination of `HV_FLUSH_*` flags.
543548
pub flags: u64,
544-
/// 64-bit processor mask (bit *i* → VP *i*). Ignored when
545-
/// `HV_FLUSH_ALL_PROCESSORS` is set in `flags`.
546-
pub processor_mask: u64,
549+
pub vp_set_format: u64,
550+
pub vp_set_valid_bank_mask: u64,
551+
pub vp_set_bank_contents: [u64; HV_FLUSH_EX_VP_SET_BANKS],
547552
}
548553

549-
/// Input structure for `HvCallFlushVirtualAddressList` (0x0003).
554+
/// Input structure for `HvCallFlushVirtualAddressListEx` (0x0014).
550555
///
551-
/// Layout (TLFS §6.5.3):
552-
/// - Fixed header: `address_space` (u64) + `flags` (u64) + `processor_mask` (u64)
556+
/// Layout (TLFS §6.5.3 EX):
557+
/// - Fixed header: `address_space` (u64), `flags` (u64)
558+
/// - Variable header: VP set (`vp_set_*` and bank contents)
553559
/// - Rep elements: array of `HV_GVA_RANGE` entries (u64 each)
554-
///
555-
/// This is the **non-extended** version; it uses a simple 64-bit processor
556-
/// mask. The GVA range list is placed immediately after the fixed header.
557560
#[derive(Clone, Copy)]
558561
#[repr(C, packed)]
559-
pub struct HvInputFlushVirtualAddressList {
560-
/// CR3 / EPTP value identifying the address space.
562+
pub struct HvInputFlushVirtualAddressListEx {
561563
pub address_space: u64,
562-
/// Combination of `HV_FLUSH_*` flags.
563564
pub flags: u64,
564-
/// 64-bit processor mask. Ignored when `HV_FLUSH_ALL_PROCESSORS` is set.
565-
pub processor_mask: u64,
566-
/// GVA range entries. Each entry specifies a GVA range to flush.
567-
/// Bits 11:0 encode `additional_pages` (number of *extra* pages beyond the
568-
/// first; 0 = flush exactly 1 page).
569-
/// Bits 63:12 encode the GVA page number (GVA >> 12).
570-
pub gva_range_list: [u64; HV_FLUSH_MAX_GVAS],
565+
pub vp_set_format: u64,
566+
pub vp_set_valid_bank_mask: u64,
567+
pub vp_set_bank_contents: [u64; HV_FLUSH_EX_VP_SET_BANKS],
568+
pub gva_range_list: [u64; HV_FLUSH_EX_MAX_GVAS],
571569
}
572570

573-
/// Maximum number of GVA range entries that fit in a single hypercall input page
574-
/// after the fixed header of `HvInputFlushVirtualAddressList`.
571+
/// Maximum number of GVA entries that fit in one input page for
572+
/// `HvInputFlushVirtualAddressListEx` with a VP set sized for `MAX_CORES`.
575573
///
576-
/// Input page = 4096 bytes. Header = 3 × 8 = 24 bytes. Remaining = 4072 / 8 = 509.
574+
/// Input page = 4096 bytes.
575+
/// Header = (4 + `HV_FLUSH_EX_VP_SET_BANKS`) * 8 bytes.
577576
#[expect(clippy::cast_possible_truncation)]
578-
const HV_FLUSH_MAX_GVAS: usize =
579-
((PAGE_SIZE as u32 - 3 * (u64::BITS / 8)) / (u64::BITS / 8)) as usize;
577+
const HV_FLUSH_EX_MAX_GVAS: usize = ((PAGE_SIZE as u32
578+
- (4 + HV_FLUSH_EX_VP_SET_BANKS as u32) * (u64::BITS / 8))
579+
/ (u64::BITS / 8)) as usize;
580+
581+
impl HvInputFlushVirtualAddressListEx {
582+
/// Number of 64-bit words occupied by the VP-set variable header.
583+
#[allow(clippy::cast_possible_truncation)]
584+
pub const VP_SET_QWORD_COUNT: u16 = (2 + HV_FLUSH_EX_VP_SET_BANKS) as u16;
580585

581-
impl HvInputFlushVirtualAddressList {
582-
/// Maximum number of GVA range entries per hypercall invocation.
583-
pub const MAX_GVAS_PER_REQUEST: usize = HV_FLUSH_MAX_GVAS;
586+
/// Maximum number of GVA range entries per EX hypercall invocation.
587+
pub const MAX_GVAS_PER_REQUEST: usize = HV_FLUSH_EX_MAX_GVAS;
584588
}
585589

586590
#[bitfield]

litebox_platform_lvbs/src/mshv/vtl_switch.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
//! VTL switch related functions
55
6+
use crate::arch::MAX_CORES;
67
use crate::arch::instrs::rdmsr;
78
use crate::host::{
89
hv_hypercall_page_address,
@@ -12,25 +13,26 @@ use crate::host::{
1213
},
1314
};
1415
use crate::mshv::{
15-
HV_REGISTER_VP_INDEX, HV_REGISTER_VSM_CODEPAGE_OFFSETS, HvRegisterVsmCodePageOffsets,
16-
NUM_VTLCALL_PARAMS, VTL_ENTRY_REASON_INTERRUPT, VTL_ENTRY_REASON_LOWER_VTL_CALL,
17-
VTL_ENTRY_REASON_RESERVED, error::VsmError, hvcall_vp::hvcall_get_vp_registers,
18-
vsm_intercept::vsm_handle_intercept,
16+
HV_FLUSH_EX_VP_SET_BANKS, HV_REGISTER_VP_INDEX, HV_REGISTER_VSM_CODEPAGE_OFFSETS,
17+
HvRegisterVsmCodePageOffsets, NUM_VTLCALL_PARAMS, VTL_ENTRY_REASON_INTERRUPT,
18+
VTL_ENTRY_REASON_LOWER_VTL_CALL, VTL_ENTRY_REASON_RESERVED, error::VsmError,
19+
hvcall_vp::hvcall_get_vp_registers, vsm_intercept::vsm_handle_intercept,
1920
};
20-
use core::sync::atomic::{AtomicU64, Ordering};
21+
use bitvec::prelude::{BitArray, Lsb0};
2122
use litebox::utils::{ReinterpretUnsignedExt, TruncateExt};
2223
use num_enum::TryFromPrimitive;
24+
use spin::mutex::SpinMutex;
2325

24-
/// Bitmask of VPs that are currently executing VTL1 code. Bit *N* is set
26+
/// Bitmask of VPs that are currently executing VTL1 code. Bit *N* is set
2527
/// when VP index *N* is inside VTL1 (between VTL entry and VTL return).
2628
///
2729
/// The TLB flush hypercalls read this mask so they only target VPs that
2830
/// are in VTL1. VPs running in VTL0 use a separate address space and
2931
/// will receive a full TLB flush on their next VTL1 entry.
3032
///
31-
/// Supports up to 64 VPs (the non-extended hypercall processor mask is
32-
/// 64 bits wide). [`vtl1_vp_enter`] asserts that the VP index fits.
33-
static VTL1_VP_MASK: AtomicU64 = AtomicU64::new(0);
33+
/// Supports up to `MAX_CORES` VPs. [`vtl1_vp_enter`] asserts that the VP index fits.
34+
type Vtl1VpMaskBits = BitArray<[u64; HV_FLUSH_EX_VP_SET_BANKS], Lsb0>;
35+
static VTL1_VP_MASK: SpinMutex<Vtl1VpMaskBits> = SpinMutex::new(Vtl1VpMaskBits::ZERO);
3436

3537
/// Read the current VP index from the hypervisor MSR.
3638
#[inline]
@@ -41,35 +43,38 @@ fn current_vp_index() -> u32 {
4143
/// Mark the current VP as executing in VTL1.
4244
///
4345
/// # Panics
44-
/// Panics (debug-only) if the VP index ≥ 64.
46+
/// Panics if the VP index ≥ `MAX_CORES`.
4547
#[inline]
4648
fn vtl1_vp_enter() {
4749
let vp_index = current_vp_index();
48-
debug_assert!(
49-
vp_index < 64,
50-
"VP index {vp_index} exceeds the 64-bit processor mask"
50+
assert!(
51+
vp_index < u32::try_from(MAX_CORES).unwrap(),
52+
"VP index {vp_index} exceeds the configured processor mask"
5153
);
52-
VTL1_VP_MASK.fetch_or(1u64 << vp_index, Ordering::Release);
54+
VTL1_VP_MASK.lock().set(vp_index as usize, true);
5355
}
5456

5557
/// Remove the current VP from the VTL1 mask (it is returning to VTL0).
5658
#[inline]
5759
fn vtl1_vp_exit() {
5860
let vp_index = current_vp_index();
59-
VTL1_VP_MASK.fetch_and(!(1u64 << vp_index), Ordering::Release);
61+
VTL1_VP_MASK.lock().set(vp_index as usize, false);
6062
}
6163

6264
/// Return the current VTL1 VP mask for use in TLB flush hypercalls.
6365
///
64-
/// The returned value is a snapshot VPs may enter or leave VTL1
66+
/// The returned value is a snapshot. VPs may enter or leave VTL1
6567
/// between the load and the hypercall, but that is benign:
6668
/// - A VP that left VTL1 after the snapshot merely receives a redundant flush.
67-
/// - A VP that entered VTL1 after the snapshot will perform a full TLB flush
68-
/// as part of the VTL transition.
69+
/// - A VP that entered VTL1 after the snapshot performs a full TLB flush
70+
/// because CR3 is reloaded at the entry and PCID is not enabled in VTL1.
6971
#[cfg(not(test))]
7072
#[inline]
71-
pub(crate) fn vtl1_vp_mask() -> u64 {
72-
VTL1_VP_MASK.load(Ordering::Acquire)
73+
pub(crate) fn vtl1_vp_mask() -> [u64; HV_FLUSH_EX_VP_SET_BANKS] {
74+
let mask = VTL1_VP_MASK.lock();
75+
let mut banks = [0u64; HV_FLUSH_EX_VP_SET_BANKS];
76+
banks.copy_from_slice(mask.as_raw_slice());
77+
banks
7378
}
7479

7580
// ============================================================================

0 commit comments

Comments
 (0)