Skip to content

feat(opentmk): opentmk framework with first testcase #1210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

mayank-microsoft
Copy link

No description provided.

feat: opentmk init

feat: opentmk init

feat: opentmk init

feat: opentmk init

feat: opentmk init

feat: opentmk init

feat: init 1

feat: init 2

feat: init 1

feat: opentmk

feat: opentmk init 3

feat: opentmk init 4

feat: opentmk init 4
@@ -22,7 +22,7 @@ pub fn create_gpt_efi_disk(out_img: &Path, with_files: &[(&Path, &Path)]) -> Res
));
}

let disk_size = 1024 * 1024 * 32; // 32MB disk should be enough for our tests
let disk_size = 1024 * 1024 * 512; // 32MB disk should be enough for our tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the increase?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the image size of the compiled application goes beyond 300MB, So we had to increase the disk size.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably make it a parameter then, so that guest-test-uefi doesn't also get bigger.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still open.

edition.workspace = true
rust-version.workspace = true

[dependencies]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sort and switch to dotted syntax for crates that don't need features. Also move all crates to be workspaced.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smalis-msft There is one issue where I want to use the serde package with default features off as I want to use a no_std env. Should I disable the feature in workspace?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you'll have to.

pub unsafe fn init(&self, size: usize) -> bool {
let pages = ((SIZE_1MB * size) / 4096) + 1;
let size = pages * 4096;
let mem: Result<core::ptr::NonNull<u8>, uefi::Error> = boot::allocate_pages(AllocateType::AnyPages, MemoryType::BOOT_SERVICES_DATA, pages);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the two different allocators?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is there to handler cases where we need to do the allocation before we exit boot services. After exit boot services we cant depend on the uefi allocator and we need to switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Is there some way we can make this cleaner? Like funadamentally splitting up these two allocators and switching between them at a higher level?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are fundamentally 2 different allocators, but they switch based on a flag.

pub mod hv_processor;
pub mod hv_misc;

pub fn run_test() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what we'll want here is some sort of type definition for a Test that has a string name and other niceties. Then we can have an array of Tests and decide what tests to run and how at runtime.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right the v1 plan is to have a config file which we can read using uefi APIs. The config can define the test to run.

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/// Writes a synthehtic register to tell the hypervisor the OS ID for the boot shim.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Writes a synthehtic register to tell the hypervisor the OS ID for the boot shim.
/// Writes a synthetic register to tell the hypervisor the OS ID for the boot shim.

@@ -0,0 +1,242 @@
#![feature(panic_location)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does "slog" refer to serial log? It's sort of an unfortunate file name.

Copy link
Author

@mayank-microsoft mayank-microsoft Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I am working on it. Steven suggested to abstract the module into two layers on how we log and the structure of the log.


const ALIGNMENT: usize = 4096;

type ComandTable =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type ComandTable =
type CommandTable =

pub trait TestCtxTrait {
fn get_vp_count(&self) -> u32;
fn get_current_vp(&self) -> u32;
fn get_current_vtl(&self) -> Vtl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this (and similar methods, like switching VTLs) for the current VP?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar questions for get_register, etc

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. these APIs are for current VP. (I'll add more documentation to explicitly call that out)


fn setup_partition_vtl(&mut self, vtl: Vtl);
fn setup_interrupt_handler(&mut self);
fn set_interupt_idx(&mut self, interrupt_idx: u8, handler: fn());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn set_interupt_idx(&mut self, interrupt_idx: u8, handler: fn());
fn set_interrupt_idx(&mut self, interrupt_idx: u8, handler: fn());

.get_mut(&vp_index)
.unwrap()
.push_back((cmd, vtl));
if vp_index == self.my_vp_idx && self.hvcall.vtl != vtl {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does vtl refer to the VTL you want the current VP to be on, or the VTL you want the target vp to run the command on?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This VTL switch is for a case where we schedule a cmd on the current VP. The current context will switch to the target VTL on current VP and execute the command. The command is expected to return to initial VTL by calling HvCall/HvReturn.


let header = hvdef::hypercall::ModifyVtlProtectionMask {
partition_id: hvdef::HV_PARTITION_ID_SELF,
map_flags: hvdef::HV_MAP_GPA_PERMISSIONS_NONE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make this an input parameter? And then other functions like apply_vtl2_protections could build on top of it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I'll work o abstracting this more.

extern "x86-interrupt" fn no_op(stack_frame: InterruptStackFrame) {}

pub fn register_interrupt_handler(idt: &mut InterruptDescriptorTable) {
register_interrupt_handler!(idt, 0, handler_0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these are architectural (and might also be in x86 defs), it might be nice to include the actual name for them.

let vtl = ctx.get_current_vtl();
infolog!("vtl: {:?}", vtl);
tmk_assert!(vtl == Vtl::Vtl1, format!("vtl should be Vtl0 for VP {}", i));
tx.send(());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually would be nice to check that the register state is what we expect, but that might be difficult if execution keeps going.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which registers would we want to assert? We can probably think of how we can do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of them, eventually. We need to make sure that we correctly get this shared/private split correct, to the extent that we can:

https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/vsm#virtual-processor-state-isolation

Comment on lines 105 to 106
val != 0xAA,
"heap memory should not be accessible from vtl0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we could eventually use the interrupt handler that you set on 0x30 to check that we did get a memory intercept?

(Pretty cool to all this in action though :))

Copy link
Contributor

@smalis-msft smalis-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies if I'm repeating something I said in the previous review, there's a lot of code here and it's been a little while.

Also, you'll need to run cargo xtask fmt and have it succeed on this code before it can be merged.

That all said, this is shaping up really really nicely. Please don't let the large number of comments discourage you, I'm really happy with how this is shaping up.

@@ -42,6 +42,9 @@ members = [
"vm/loader/igvmfilegen",
"vm/vmgs/vmgs_lib",
"vm/vmgs/vmgstool",
# opentmk
"opentmk/opentmk",
"opentmk/sync"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If something is a dependency below it shouldn't be a member, members only needs to be top-level crates.

@@ -520,6 +529,7 @@ winapi = "0.3"
windows = "0.59"
windows-service = "0.7"
windows-sys = "0.52"
x86_64 = "0.15.2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much functionality from x86_64 are we using? How difficult of a lift would it be to fill in things like x86defs and safe_intrinsics, or just call intrinsics directly?

@@ -445,9 +451,11 @@ jiff = "0.1"
kvm-bindings = "0.7"
# Use of these specific REPO will go away when changes are taken upstream.
landlock = "0.3.1"
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't specify features in the workspace-level Cargo.toml, since that applies them to every crate whether they want them or not. Move it into the specific crates' Cargo.tomls that need it.

@@ -22,7 +22,7 @@ pub fn create_gpt_efi_disk(out_img: &Path, with_files: &[(&Path, &Path)]) -> Res
));
}

let disk_size = 1024 * 1024 * 32; // 32MB disk should be enough for our tests
let disk_size = 1024 * 1024 * 512; // 32MB disk should be enough for our tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still open.


[[package]]
name = "spin"
version = "0.10.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we unify on the same version of spin that lazy_static is pulling in, so we only have the one?

unsafe {
self.locked_heap.lock().init(ptr, size);
}
*self.use_locked_heap.lock().borrow_mut() = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're always setting this to true do we still need the uefi allocator? Can we get away with just using the linked_list_allocator directly in all cases?

If so, is there some way we could make this type be an enum with a variant for each allocator instead of keeping both around forever?

@@ -0,0 +1,13 @@
#![allow(dead_code)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: get rid of all these allow(dead_code)s everywhere


//! Imports and re-exports architecture-specific implementations.

mod x86_64;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not building the arm code currently?


pub fn run_test() {
let mut ctx = HvTestCtx::new();
hv_processor::exec(&mut ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between the tests in here and the tests in uefi/ ?

///
/// The caller must be sure that the given port is safe to write to, and that the
/// given value is safe for it.
unsafe fn outb(port: u16, data: u8) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this stuff all shared with the boot shim too? If so we should try to move it somewhere shared instead of duplicating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants