Skip to content

Commit

Permalink
vmbus_relay: split HCL driver interactions to new crate (#426)
Browse files Browse the repository at this point in the history
Instead of hard-coding the interactions with the Linux HCL driver, go
through a trait object. Extend the same `SynicClient` trait that we
already use.

After additional changes, this trait will be consumed only by
`vmbus_client` and won't be used in `vmbus_relay` or
`vmbus_relay_intercept_device` at all.
  • Loading branch information
jstarks authored Dec 6, 2024
1 parent fa10a92 commit fc46c7b
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 154 deletions.
22 changes: 18 additions & 4 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6892,6 +6892,7 @@ dependencies = [
"vm_topology",
"vmbus_async",
"vmbus_channel",
"vmbus_client_hcl",
"vmbus_core",
"vmbus_relay",
"vmbus_relay_intercept_device",
Expand Down Expand Up @@ -7750,6 +7751,7 @@ dependencies = [
"inspect",
"mesh",
"pal_async",
"pal_event",
"parking_lot",
"thiserror 2.0.0",
"tracing",
Expand All @@ -7759,6 +7761,22 @@ dependencies = [
"zerocopy",
]

[[package]]
name = "vmbus_client_hcl"
version = "0.0.0"
dependencies = [
"anyhow",
"futures",
"hcl",
"hvdef",
"pal_async",
"pal_event",
"tracing",
"vmbus_async",
"vmbus_client",
"zerocopy",
]

[[package]]
name = "vmbus_core"
version = "0.0.0"
Expand Down Expand Up @@ -7798,8 +7816,6 @@ dependencies = [
"anyhow",
"futures",
"guid",
"hcl",
"hvdef",
"inspect",
"mesh",
"mesh_protobuf",
Expand All @@ -7809,7 +7825,6 @@ dependencies = [
"parking_lot",
"tracelimit",
"tracing",
"vmbus_async",
"vmbus_channel",
"vmbus_client",
"vmbus_core",
Expand All @@ -7825,7 +7840,6 @@ dependencies = [
"anyhow",
"futures",
"guid",
"hcl",
"hvdef",
"inspect",
"mesh",
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ vmbfs_resources = { path = "vm/devices/vmbus/vmbfs_resources" }
vmbus_async = { path = "vm/devices/vmbus/vmbus_async" }
vmbus_channel = { path = "vm/devices/vmbus/vmbus_channel" }
vmbus_client = { path = "vm/devices/vmbus/vmbus_client" }
vmbus_client_hcl = { path = "vm/devices/vmbus/vmbus_client_hcl" }
vmbus_core = { path = "vm/devices/vmbus/vmbus_core" }
vmbus_proxy = { path = "vm/devices/vmbus/vmbus_proxy" }
vmbus_relay = { path = "vm/devices/vmbus/vmbus_relay" }
Expand Down
1 change: 1 addition & 0 deletions openhcl/underhill_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ vmbus_async.workspace = true
vmbus_user_channel.workspace = true
vmbus_channel.workspace = true
vmbus_core.workspace = true
vmbus_client_hcl.workspace = true
vmbus_relay.workspace = true
vmbus_relay_intercept_device.workspace = true
vmbus_serial_guest.workspace = true
Expand Down
17 changes: 14 additions & 3 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2602,6 +2602,7 @@ async fn new_underhill_vm(

let mut vmbus_server = None;
let mut host_vmbus_relay = None;
let mut vmbus_synic_client = None;

// VMBus
if with_vmbus {
Expand Down Expand Up @@ -2664,15 +2665,24 @@ async fn new_underhill_vm(

let vmbus = VmbusServerHandle::new(&tp, state_units.add("vmbus"), vmbus)?;
if let Some((relay_channel, hvsock_relay)) = relay_channels {
let relay_driver = tp.driver(0);
let (synic, msg_source) =
vmbus_client_hcl::new_synic_client_and_messsage_source(relay_driver)
.context("failed to create synic client and message source")?;

let synic = Arc::new(synic);

let vmbus_relay = vmbus_relay::HostVmbusTransport::new(
tp.driver(0).clone(),
relay_driver.clone(),
Arc::clone(vmbus.control()),
relay_channel,
hvsock_relay,
synic.clone(),
msg_source,
)
.await
.expect("failed to create host vmbus transport");
.context("failed to create host vmbus transport")?;

vmbus_synic_client = Some(synic);
host_vmbus_relay = Some(VmbusRelayHandle::new(
&tp,
state_units
Expand Down Expand Up @@ -2848,6 +2858,7 @@ async fn new_underhill_vm(
let shutdown_guest = SimpleVmbusClientDeviceWrapper::new(
driver_source.simple(),
partition.clone(),
vmbus_synic_client.clone().unwrap(),
shutdown_guest,
)?;
vmbus_intercept_devices
Expand Down
1 change: 1 addition & 0 deletions vm/devices/vmbus/vmbus_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ vmbus_core.workspace = true
guid.workspace = true
mesh.workspace = true
pal_async.workspace = true
pal_event.workspace = true
inspect.workspace = true

anyhow.workspace = true
Expand Down
27 changes: 23 additions & 4 deletions vm/devices/vmbus/vmbus_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use pal_async::task::Spawn;
use pal_async::task::Task;
use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::Arc;
use thiserror::Error;
use vmbus_async::async_dgram::AsyncRecv;
use vmbus_async::async_dgram::AsyncRecvExt;
Expand Down Expand Up @@ -49,6 +50,12 @@ const SUPPORTED_FEATURE_FLAGS: FeatureFlags = FeatureFlags::all();
/// The client interface to the synic.
pub trait SynicClient: Send + Sync {
fn post_message(&self, connection_id: u32, typ: u32, msg: &[u8]);
/// Maps an incoming event signal on SINT7 to `event`.
fn map_event(&self, event_flag: u16, event: &pal_event::Event) -> std::io::Result<()>;
/// Unmaps an event previously mapped with `map_event`.
fn unmap_event(&self, event_flag: u16);
/// Signals an event on the synic.
fn signal_event(&self, connection_id: u32, event_flag: u16) -> std::io::Result<()>;
}

/// A stream of vmbus messages that can be paused and resumed.
Expand Down Expand Up @@ -80,7 +87,7 @@ pub enum ConnectError {
impl VmbusClient {
/// Creates a new instance with a receiver for incoming synic messages.
pub fn new(
synic: impl 'static + SynicClient,
synic: Arc<dyn SynicClient>,
notify_send: mesh::Sender<ClientNotification>,
msg_source: impl VmbusMessageSource + 'static,
spawner: &impl Spawn,
Expand All @@ -89,7 +96,7 @@ impl VmbusClient {
let (client_request_send, client_request_recv) = mesh::channel();

let inner = ClientTaskInner {
synic: Box::new(synic),
synic,
channels: HashMap::new(),
gpadls: HashMap::new(),
teardown_gpadls: HashMap::new(),
Expand Down Expand Up @@ -1312,7 +1319,7 @@ enum GpadlState {
}

struct ClientTaskInner {
synic: Box<dyn SynicClient>,
synic: Arc<dyn SynicClient>,
channels: HashMap<ChannelId, Channel>,
gpadls: HashMap<(ChannelId, GpadlId), GpadlState>,
teardown_gpadls: HashMap<GpadlId, Option<ChannelId>>,
Expand Down Expand Up @@ -1471,6 +1478,18 @@ mod tests {
.lock()
.push(OutgoingMessage::from_message(msg));
}

fn map_event(&self, _event_flag: u16, _event: &pal_event::Event) -> std::io::Result<()> {
Err(std::io::ErrorKind::Unsupported.into())
}

fn unmap_event(&self, _event_flag: u16) {
unreachable!()
}

fn signal_event(&self, _connection_id: u32, _event_flag: u16) -> std::io::Result<()> {
Err(std::io::ErrorKind::Unsupported.into())
}
}

struct TestMessageSource {
Expand Down Expand Up @@ -1515,7 +1534,7 @@ mod tests {
let (notify_send, notify_recv) = mesh::channel();

let mut client = VmbusClient::new(
server.clone(),
Arc::new(server.clone()),
notify_send,
TestMessageSource { msg_recv },
&driver,
Expand Down
23 changes: 23 additions & 0 deletions vm/devices/vmbus/vmbus_client_hcl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[package]
name = "vmbus_client_hcl"
edition = "2021"
rust-version.workspace = true

[target.'cfg(target_os = "linux")'.dependencies]
hcl.workspace = true
hvdef.workspace = true
pal_async.workspace = true
pal_event.workspace = true
vmbus_async.workspace = true
vmbus_client.workspace = true

anyhow.workspace = true
futures.workspace = true
tracing.workspace = true
zerocopy.workspace = true

[lints]
workspace = true
148 changes: 148 additions & 0 deletions vm/devices/vmbus/vmbus_client_hcl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![cfg(target_os = "linux")]

//! Implementation of [`vmbus_client`] traits to communicate with the synic via
//! the Linux HCL driver.
#![warn(missing_docs)]
#![forbid(unsafe_code)]

use anyhow::Context as _;
use futures::AsyncRead;
use hcl::ioctl::HypercallError;
use hcl::vmbus::HclVmbus;
use hvdef::HvError;
use hvdef::HvMessage;
use hvdef::HvMessageHeader;
use pal_async::driver::Driver;
use pal_async::pipe::PolledPipe;
use std::io;
use std::io::IoSliceMut;
use std::os::fd::AsFd;
use std::pin::Pin;
use std::sync::Arc;
use std::task::ready;
use std::task::Poll;
use vmbus_async::async_dgram::AsyncRecv;
use vmbus_client::SynicClient;
use vmbus_client::VmbusMessageSource;
use zerocopy::AsBytes;

/// Returns the synic client and message source for use with
/// [`vmbus_client::VmbusClient`].
pub fn new_synic_client_and_messsage_source(
driver: &(impl Driver + ?Sized),
) -> anyhow::Result<(impl SynicClient, impl VmbusMessageSource)> {
// Open an HCL vmbus fd for issuing synic requests.
let hcl_vmbus = Arc::new(HclVmbus::new().context("failed to open hcl_vmbus")?);
let synic = HclSynic {
hcl_vmbus: Arc::clone(&hcl_vmbus),
};

// Open another one for polling for messages.
let vmbus_fd = HclVmbus::new()
.context("failed to open hcl_vmbus")?
.into_inner();

let pipe = PolledPipe::new(driver, vmbus_fd).context("failed to created PolledPipe")?;
let msg_source = MessageSource {
pipe,
hcl_vmbus: Arc::clone(&hcl_vmbus),
};

Ok((synic, msg_source))
}

struct HclSynic {
hcl_vmbus: Arc<HclVmbus>,
}

impl SynicClient for HclSynic {
fn post_message(&self, connection_id: u32, typ: u32, msg: &[u8]) {
let mut tries = 0;
let mut wait = 1;
// If we receive HV_STATUS_INSUFFICIENT_BUFFERS block till the call is
// successful with a delay.
loop {
let ret = self.hcl_vmbus.post_message(connection_id, typ.into(), msg);
match ret {
Ok(()) => break,
Err(HypercallError::Hypervisor(HvError::InsufficientBuffers)) => {
tracing::debug!("received HV_STATUS_INSUFFICIENT_BUFFERS, retrying");
if tries < 22 {
wait *= 2;
tries += 1;
}
std::thread::sleep(std::time::Duration::from_millis(wait / 1000));
}
Err(err) => {
panic!("received error code from post message call {}", err);
}
}
}
}

fn map_event(&self, event_flag: u16, event: &pal_event::Event) -> io::Result<()> {
self.hcl_vmbus
.set_eventfd(event_flag.into(), Some(event.as_fd()))
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}

fn unmap_event(&self, event_flag: u16) {
self.hcl_vmbus.set_eventfd(event_flag.into(), None).unwrap();
}

fn signal_event(&self, connection_id: u32, event_flag: u16) -> io::Result<()> {
self.hcl_vmbus
.signal_event(connection_id, event_flag.into())
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
}
}

struct MessageSource {
pipe: PolledPipe,
hcl_vmbus: Arc<HclVmbus>,
}

impl AsyncRecv for MessageSource {
fn poll_recv(
&mut self,
cx: &mut std::task::Context<'_>,
mut bufs: &mut [IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
let mut msg = HvMessage::default();
let size = ready!(Pin::new(&mut self.pipe).poll_read(cx, msg.as_bytes_mut()))?;
if size == 0 {
return Ok(0).into();
}

assert!(size >= size_of::<HvMessageHeader>());
let mut remaining = msg.payload();
let mut total_size = 0;
while !remaining.is_empty() && !bufs.is_empty() {
let size = bufs[0].len().min(remaining.len());
bufs[0][..size].copy_from_slice(&remaining[..size]);
remaining = &remaining[size..];
bufs = &mut bufs[1..];
total_size += size;
}

Ok(total_size).into()
}
}

impl VmbusMessageSource for MessageSource {
fn pause_message_stream(&mut self) {
self.hcl_vmbus
.pause_message_stream(true)
.expect("Unable to disable HCL vmbus message stream.");
}

fn resume_message_stream(&mut self) {
self.hcl_vmbus
.pause_message_stream(false)
.expect("Unable to enable HCL vmbus message stream.");
}
}
3 changes: 0 additions & 3 deletions vm/devices/vmbus/vmbus_relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ edition = "2021"
rust-version.workspace = true

[dependencies]
vmbus_async.workspace = true
vmbus_channel.workspace = true
vmbus_client.workspace = true
vmbus_core.workspace = true
vmbus_server.workspace = true

guid.workspace = true
hvdef.workspace = true
vmcore.workspace = true
hcl.workspace = true
inspect.workspace = true
mesh.workspace = true
mesh_protobuf.workspace = true
Expand Down
Loading

0 comments on commit fc46c7b

Please sign in to comment.