mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 13:11:31 +00:00
feat(xen): update xenclient and xenplatform to the latest structure (#433)
This commit is contained in:
@ -9,6 +9,8 @@ edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bit-vec = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
log = { workspace = true }
|
||||
krata-xencall = { path = "../xencall", version = "^0.0.23" }
|
||||
|
@ -1,15 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
use std::{env, process};
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
use xenclient::error::Result;
|
||||
use xenclient::{DomainConfig, XenClient};
|
||||
use xenplatform::domain::BaseDomainConfig;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
type RuntimePlatform = xenplatform::x86pv::X86PvPlatform;
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform;
|
||||
use xenclient::tx::channel::ChannelDeviceConfig;
|
||||
use xenclient::{config::DomainConfig, XenClient};
|
||||
use xenplatform::domain::{
|
||||
KernelFormat, PlatformDomainConfig, PlatformKernelConfig, PlatformOptions,
|
||||
PlatformResourcesConfig,
|
||||
};
|
||||
use xenplatform::RuntimePlatformType;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@ -22,32 +22,31 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
let kernel_image_path = args.get(1).expect("argument not specified");
|
||||
let initrd_path = args.get(2).expect("argument not specified");
|
||||
let client = XenClient::new(0, RuntimePlatform::new()).await?;
|
||||
let config = DomainConfig {
|
||||
base: BaseDomainConfig {
|
||||
uuid: Uuid::new_v4(),
|
||||
max_vcpus: 1,
|
||||
target_vcpus: 1,
|
||||
max_mem_mb: 512,
|
||||
target_mem_mb: 512,
|
||||
enable_iommu: true,
|
||||
kernel: fs::read(&kernel_image_path).await?,
|
||||
initrd: fs::read(&initrd_path).await?,
|
||||
let client = XenClient::new().await?;
|
||||
|
||||
let mut config = DomainConfig::new();
|
||||
config.platform(PlatformDomainConfig {
|
||||
uuid: Uuid::new_v4(),
|
||||
platform: RuntimePlatformType::Pv,
|
||||
kernel: PlatformKernelConfig {
|
||||
data: Arc::new(fs::read(&kernel_image_path).await?),
|
||||
format: KernelFormat::ElfCompressed,
|
||||
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
|
||||
owner_domid: 0,
|
||||
initrd: Some(Arc::new(fs::read(&initrd_path).await?)),
|
||||
},
|
||||
backend_domid: 0,
|
||||
name: "xenclient-test".to_string(),
|
||||
swap_console_backend: None,
|
||||
disks: vec![],
|
||||
channels: vec![],
|
||||
vifs: vec![],
|
||||
pcis: vec![],
|
||||
filesystems: vec![],
|
||||
extra_keys: vec![],
|
||||
extra_rw_paths: vec![],
|
||||
};
|
||||
let created = client.create(&config).await?;
|
||||
println!("created domain {}", created.domid);
|
||||
resources: PlatformResourcesConfig {
|
||||
max_vcpus: 1,
|
||||
assigned_vcpus: 1,
|
||||
max_memory_mb: 512,
|
||||
assigned_memory_mb: 512,
|
||||
},
|
||||
options: PlatformOptions { iommu: true },
|
||||
});
|
||||
config.name("xenclient-test");
|
||||
let mut channel = ChannelDeviceConfig::new();
|
||||
channel.default_console().backend_initialized();
|
||||
config.add_channel(channel);
|
||||
let created = client.create(config).await?;
|
||||
println!("created domain {}", created.platform.domid);
|
||||
Ok(())
|
||||
}
|
||||
|
66
crates/xen/xenclient/examples/boot_speed.rs
Normal file
66
crates/xen/xenclient/examples/boot_speed.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::sync::Arc;
|
||||
use std::{env, process};
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
use xenclient::config::{DomainConfig, DomainResult};
|
||||
use xenclient::error::Result;
|
||||
use xenclient::tx::channel::ChannelDeviceConfig;
|
||||
use xenclient::XenClient;
|
||||
use xenplatform::domain::{
|
||||
KernelFormat, PlatformDomainConfig, PlatformKernelConfig, PlatformOptions,
|
||||
PlatformResourcesConfig,
|
||||
};
|
||||
use xenplatform::elfloader::ElfImageLoader;
|
||||
use xenplatform::RuntimePlatformType;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
println!("usage: boot-speed <kernel-image>");
|
||||
process::exit(1);
|
||||
}
|
||||
let kernel_path = args.get(1).expect("argument not specified");
|
||||
let kernel = Arc::new(fs::read(kernel_path).await?);
|
||||
let kernel = ElfImageLoader::load(kernel)?.into_elf_bytes();
|
||||
let client = XenClient::new().await?;
|
||||
|
||||
for i in 0..5u32 {
|
||||
let start = std::time::Instant::now();
|
||||
let domain = create_domain(&client, kernel.clone(), i).await?;
|
||||
let end = std::time::Instant::now();
|
||||
let duration = end - start;
|
||||
println!("boot setup time: {:?}", duration);
|
||||
client.destroy(domain.platform.domid).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_domain(client: &XenClient, kernel: Arc<Vec<u8>>, i: u32) -> Result<DomainResult> {
|
||||
let mut config = DomainConfig::new();
|
||||
config.platform(PlatformDomainConfig {
|
||||
uuid: Uuid::new_v4(),
|
||||
platform: RuntimePlatformType::Pv,
|
||||
kernel: PlatformKernelConfig {
|
||||
data: kernel,
|
||||
format: KernelFormat::ElfUncompressed,
|
||||
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
|
||||
initrd: None,
|
||||
},
|
||||
resources: PlatformResourcesConfig {
|
||||
max_vcpus: 1,
|
||||
assigned_vcpus: 1,
|
||||
max_memory_mb: 512,
|
||||
assigned_memory_mb: 512,
|
||||
},
|
||||
options: PlatformOptions { iommu: true },
|
||||
});
|
||||
config.name(format!("xenboot-{}", i));
|
||||
config.start(false);
|
||||
let mut channel = ChannelDeviceConfig::new();
|
||||
channel.default_console().backend_initialized();
|
||||
config.add_channel(channel);
|
||||
client.create(config).await
|
||||
}
|
185
crates/xen/xenclient/src/config.rs
Normal file
185
crates/xen/xenclient/src/config.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use xencall::XenCall;
|
||||
pub use xenplatform::domain::PlatformDomainConfig;
|
||||
use xenplatform::domain::PlatformDomainInfo;
|
||||
|
||||
use crate::{
|
||||
error::Result,
|
||||
tx::{
|
||||
channel::ChannelDeviceConfig,
|
||||
fs9p::Fs9pDeviceConfig,
|
||||
pci::PciRootDeviceConfig,
|
||||
vbd::VbdDeviceConfig,
|
||||
vif::VifDeviceConfig,
|
||||
{BlockDeviceResult, DeviceResult},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct DomainConfig {
|
||||
platform: Option<PlatformDomainConfig>,
|
||||
name: Option<String>,
|
||||
backend_domid: u32,
|
||||
channels: Vec<ChannelDeviceConfig>,
|
||||
vifs: Vec<VifDeviceConfig>,
|
||||
vbds: Vec<VbdDeviceConfig>,
|
||||
fs9ps: Vec<Fs9pDeviceConfig>,
|
||||
pci: Option<PciRootDeviceConfig>,
|
||||
extra_keys: HashMap<String, String>,
|
||||
extra_rw_paths: Vec<String>,
|
||||
start: bool,
|
||||
}
|
||||
|
||||
impl Default for DomainConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl DomainConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
platform: None,
|
||||
name: None,
|
||||
backend_domid: 0,
|
||||
channels: Vec::new(),
|
||||
vifs: Vec::new(),
|
||||
vbds: Vec::new(),
|
||||
fs9ps: Vec::new(),
|
||||
pci: None,
|
||||
extra_keys: HashMap::new(),
|
||||
extra_rw_paths: Vec::new(),
|
||||
start: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn platform(&mut self, platform: PlatformDomainConfig) -> &mut Self {
|
||||
self.platform = Some(platform);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_platform(&self) -> &Option<PlatformDomainConfig> {
|
||||
&self.platform
|
||||
}
|
||||
|
||||
pub fn name(&mut self, name: impl AsRef<str>) -> &mut Self {
|
||||
self.name = Some(name.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> &Option<String> {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn backend_domid(&mut self, backend_domid: u32) -> &mut Self {
|
||||
self.backend_domid = backend_domid;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_backend_domid(&self) -> u32 {
|
||||
self.backend_domid
|
||||
}
|
||||
|
||||
pub fn add_channel(&mut self, channel: ChannelDeviceConfig) -> &mut Self {
|
||||
self.channels.push(channel);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_channels(&self) -> &Vec<ChannelDeviceConfig> {
|
||||
&self.channels
|
||||
}
|
||||
|
||||
pub fn add_vif(&mut self, vif: VifDeviceConfig) -> &mut Self {
|
||||
self.vifs.push(vif);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_vifs(&self) -> &Vec<VifDeviceConfig> {
|
||||
&self.vifs
|
||||
}
|
||||
|
||||
pub fn add_vbd(&mut self, vbd: VbdDeviceConfig) -> &mut Self {
|
||||
self.vbds.push(vbd);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_vbds(&self) -> &Vec<VbdDeviceConfig> {
|
||||
&self.vbds
|
||||
}
|
||||
|
||||
pub fn add_fs9p(&mut self, fs9p: Fs9pDeviceConfig) -> &mut Self {
|
||||
self.fs9ps.push(fs9p);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_fs9ps(&self) -> &Vec<Fs9pDeviceConfig> {
|
||||
&self.fs9ps
|
||||
}
|
||||
|
||||
pub fn pci(&mut self, pci: PciRootDeviceConfig) -> &mut Self {
|
||||
self.pci = Some(pci);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_pci(&self) -> &Option<PciRootDeviceConfig> {
|
||||
&self.pci
|
||||
}
|
||||
|
||||
pub fn add_extra_key(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
|
||||
self.extra_keys
|
||||
.insert(key.as_ref().to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_extra_keys(&self) -> &HashMap<String, String> {
|
||||
&self.extra_keys
|
||||
}
|
||||
|
||||
pub fn add_rw_path(&mut self, path: impl AsRef<str>) -> &mut Self {
|
||||
self.extra_rw_paths.push(path.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_rw_paths(&self) -> &Vec<String> {
|
||||
&self.extra_rw_paths
|
||||
}
|
||||
|
||||
pub fn start(&mut self, start: bool) -> &mut Self {
|
||||
self.start = start;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_start(&self) -> bool {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare(
|
||||
&mut self,
|
||||
domid: u32,
|
||||
call: &XenCall,
|
||||
platform: &PlatformDomainInfo,
|
||||
) -> Result<()> {
|
||||
if let Some(pci) = self.pci.as_mut() {
|
||||
pci.prepare(domid, call).await?;
|
||||
}
|
||||
|
||||
for channel in &mut self.channels {
|
||||
channel.prepare(platform).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DomainResult {
|
||||
pub platform: PlatformDomainInfo,
|
||||
pub channels: Vec<DeviceResult>,
|
||||
pub vifs: Vec<DeviceResult>,
|
||||
pub vbds: Vec<BlockDeviceResult>,
|
||||
pub fs9ps: Vec<DeviceResult>,
|
||||
pub pci: Option<DeviceResult>,
|
||||
}
|
79
crates/xen/xenclient/src/devalloc.rs
Normal file
79
crates/xen/xenclient/src/devalloc.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use bit_vec::BitVec;
|
||||
|
||||
const DEVICE_COUNT: usize = 4096;
|
||||
const BYTE_COUNT: usize = DEVICE_COUNT / 8;
|
||||
|
||||
pub struct DeviceIdAllocator {
|
||||
states: BitVec,
|
||||
cursor: u32,
|
||||
}
|
||||
|
||||
impl Default for DeviceIdAllocator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceIdAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
states: BitVec::from_elem(DEVICE_COUNT, false),
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> Option<Self> {
|
||||
if bytes.len() != BYTE_COUNT + 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cursor = bytes[0] as u32
|
||||
| ((bytes[1] as u32) << 8)
|
||||
| ((bytes[2] as u32) << 16)
|
||||
| ((bytes[3] as u32) << 24);
|
||||
let slice = &bytes[4..BYTE_COUNT + 4];
|
||||
if slice.len() != BYTE_COUNT {
|
||||
return None;
|
||||
}
|
||||
let states = BitVec::from_bytes(slice);
|
||||
|
||||
Some(Self { states, cursor })
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self) -> Option<u32> {
|
||||
let start = self.cursor;
|
||||
loop {
|
||||
let id = self.cursor;
|
||||
let value = self.states.get(self.cursor as usize)?;
|
||||
|
||||
self.cursor = (self.cursor + 1) % DEVICE_COUNT as u32;
|
||||
|
||||
if !value {
|
||||
self.states.set(id as usize, true);
|
||||
return Some(id);
|
||||
}
|
||||
|
||||
if self.cursor == start {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release(&mut self, id: u32) {
|
||||
self.states.set(id as usize, false);
|
||||
}
|
||||
|
||||
pub fn count_free(&mut self) -> u32 {
|
||||
self.states.count_zeros() as u32
|
||||
}
|
||||
|
||||
pub fn serialize(&mut self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(BYTE_COUNT + 4);
|
||||
bytes.push((self.cursor & 0xff) as u8);
|
||||
bytes.push(((self.cursor >> 8) & 0xff) as u8);
|
||||
bytes.push(((self.cursor >> 16) & 0xff) as u8);
|
||||
bytes.push(((self.cursor >> 24) & 0xff) as u8);
|
||||
bytes.extend_from_slice(&self.states.to_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
131
crates/xen/xenclient/src/devstate.rs
Normal file
131
crates/xen/xenclient/src/devstate.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::{
|
||||
select,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
use xenstore::{XsdClient, XsdInterface};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct DeviceLocator {
|
||||
pub frontend_domid: u32,
|
||||
pub backend_domid: u32,
|
||||
pub frontend_type: String,
|
||||
pub backend_type: String,
|
||||
pub device_id: u64,
|
||||
}
|
||||
|
||||
impl DeviceLocator {
|
||||
pub fn new(
|
||||
frontend_domid: u32,
|
||||
backend_domid: u32,
|
||||
frontend_type: String,
|
||||
backend_type: String,
|
||||
device_id: u64,
|
||||
) -> Self {
|
||||
DeviceLocator {
|
||||
frontend_domid,
|
||||
backend_domid,
|
||||
frontend_type,
|
||||
backend_type,
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frontend_state_path(&self) -> String {
|
||||
format!(
|
||||
"/local/domain/{}/device/{}/{}/state",
|
||||
self.frontend_domid, self.frontend_type, self.device_id
|
||||
)
|
||||
}
|
||||
|
||||
pub fn backend_state_path(&self) -> String {
|
||||
format!(
|
||||
"/local/domain/{}/backend/{}/{}/{}/state",
|
||||
self.backend_domid, self.backend_type, self.frontend_domid, self.device_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceStateWaiter {
|
||||
devices: Vec<DeviceLocator>,
|
||||
xsd: XsdClient,
|
||||
}
|
||||
|
||||
impl DeviceStateWaiter {
|
||||
pub fn new(xsd: XsdClient) -> Self {
|
||||
DeviceStateWaiter {
|
||||
devices: vec![],
|
||||
xsd,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_device(&mut self, device: DeviceLocator) -> &mut DeviceStateWaiter {
|
||||
self.devices.push(device);
|
||||
self
|
||||
}
|
||||
|
||||
async fn check_states(xsd: &XsdClient, state_paths: &[String], desired: u32) -> Result<bool> {
|
||||
let mut ready = 0;
|
||||
for state_path in state_paths {
|
||||
let Some(state_text) = xsd.read_string(state_path).await? else {
|
||||
return Err(Error::DevStateWaitError(format!(
|
||||
"state path '{}' did not exist",
|
||||
state_path
|
||||
)));
|
||||
};
|
||||
|
||||
let Some(state_value) = state_text.parse::<u32>().ok() else {
|
||||
return Err(Error::DevStateWaitError(format!(
|
||||
"state path '{}' did not have a valid value",
|
||||
state_path
|
||||
)));
|
||||
};
|
||||
|
||||
if state_value > desired {
|
||||
return Err(Error::DevStateWaitError(format!(
|
||||
"state path '{}' had a state of {} which is greater than {}",
|
||||
state_path, state_value, desired
|
||||
)));
|
||||
}
|
||||
|
||||
if state_value == desired {
|
||||
ready += 1;
|
||||
}
|
||||
}
|
||||
Ok(ready == state_paths.len())
|
||||
}
|
||||
|
||||
async fn do_wait(self, desired: u32) -> Result<()> {
|
||||
let mut watch = self.xsd.create_multi_watch().await?;
|
||||
let mut state_paths = Vec::new();
|
||||
for device in self.devices {
|
||||
let state_path = device.backend_state_path();
|
||||
self.xsd.bind_watch_id(watch.id, &state_path).await?;
|
||||
state_paths.push(state_path);
|
||||
}
|
||||
|
||||
loop {
|
||||
if DeviceStateWaiter::check_states(&self.xsd, &state_paths, desired).await? {
|
||||
break;
|
||||
}
|
||||
|
||||
select! {
|
||||
_update = watch.receiver.recv() => {},
|
||||
_timeout = sleep(Duration::from_millis(250)) => {},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn wait(self, desired: u32, deadline: Duration) -> Result<()> {
|
||||
if let Some(err) = timeout(deadline, self.do_wait(desired)).await.err() {
|
||||
return Err(Error::DevStateWaitError(format!(
|
||||
"took too long for devices to be ready: {}",
|
||||
err
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ pub enum Error {
|
||||
RegexError(#[from] regex::Error),
|
||||
#[error("error: {0}")]
|
||||
GenericError(String),
|
||||
#[error("parameter missing: {0}")]
|
||||
ParameterMissing(&'static str),
|
||||
#[error("failed to parse int: {0}")]
|
||||
ParseIntError(#[from] std::num::ParseIntError),
|
||||
#[error("invalid pci bdf string")]
|
||||
@ -42,6 +44,12 @@ pub enum Error {
|
||||
PciDeviceNotAssignable(PciBdf),
|
||||
#[error("xen platform error: {0}")]
|
||||
XenPlatform(#[from] xenplatform::error::Error),
|
||||
#[error("invalid block index")]
|
||||
InvalidBlockIdx,
|
||||
#[error("device state wait error: {0}")]
|
||||
DevStateWaitError(String),
|
||||
#[error("device ids exhausted")]
|
||||
DevIdExhausted,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
@ -1,12 +1,11 @@
|
||||
pub mod error;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use config::{DomainConfig, DomainResult};
|
||||
use error::{Error, Result};
|
||||
use log::{debug, trace};
|
||||
use pci::PciBdf;
|
||||
use tokio::time::timeout;
|
||||
use tx::ClientTransaction;
|
||||
use xenplatform::boot::BootSetupPlatform;
|
||||
use xenplatform::domain::{BaseDomainConfig, BaseDomainManager, CreatedDomain};
|
||||
use tx::{DeviceConfig, XenTransaction};
|
||||
use xenplatform::domain::{PlatformDomainInfo, PlatformDomainManager};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@ -15,109 +14,26 @@ use std::time::Duration;
|
||||
use xencall::XenCall;
|
||||
use xenstore::{XsdClient, XsdInterface};
|
||||
|
||||
pub mod config;
|
||||
pub mod devalloc;
|
||||
pub mod devstate;
|
||||
pub mod pci;
|
||||
pub mod tx;
|
||||
pub mod util;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct XenClient<P: BootSetupPlatform> {
|
||||
pub struct XenClient {
|
||||
pub store: XsdClient,
|
||||
pub call: XenCall,
|
||||
domain_manager: Arc<BaseDomainManager<P>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlockDeviceRef {
|
||||
pub path: String,
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainDisk {
|
||||
pub vdev: String,
|
||||
pub block: BlockDeviceRef,
|
||||
pub writable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainFilesystem {
|
||||
pub path: String,
|
||||
pub tag: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainNetworkInterface {
|
||||
pub mac: String,
|
||||
pub mtu: u32,
|
||||
pub bridge: Option<String>,
|
||||
pub script: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainChannel {
|
||||
pub typ: String,
|
||||
pub initialized: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainEventChannel {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum DomainPciRdmReservePolicy {
|
||||
Invalid,
|
||||
#[default]
|
||||
Strict,
|
||||
Relaxed,
|
||||
}
|
||||
|
||||
impl DomainPciRdmReservePolicy {
|
||||
pub fn to_option_str(&self) -> &str {
|
||||
match self {
|
||||
DomainPciRdmReservePolicy::Invalid => "-1",
|
||||
DomainPciRdmReservePolicy::Strict => "0",
|
||||
DomainPciRdmReservePolicy::Relaxed => "1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainPciDevice {
|
||||
pub bdf: PciBdf,
|
||||
pub permissive: bool,
|
||||
pub msi_translate: bool,
|
||||
pub power_management: bool,
|
||||
pub rdm_reserve_policy: DomainPciRdmReservePolicy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DomainConfig {
|
||||
pub base: BaseDomainConfig,
|
||||
pub backend_domid: u32,
|
||||
pub name: String,
|
||||
pub disks: Vec<DomainDisk>,
|
||||
pub swap_console_backend: Option<String>,
|
||||
pub channels: Vec<DomainChannel>,
|
||||
pub vifs: Vec<DomainNetworkInterface>,
|
||||
pub filesystems: Vec<DomainFilesystem>,
|
||||
pub pcis: Vec<DomainPciDevice>,
|
||||
pub extra_keys: Vec<(String, String)>,
|
||||
pub extra_rw_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CreatedChannel {
|
||||
pub ring_ref: u64,
|
||||
pub evtchn: u32,
|
||||
domain_manager: Arc<PlatformDomainManager>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl<P: BootSetupPlatform> XenClient<P> {
|
||||
pub async fn new(current_domid: u32, platform: P) -> Result<XenClient<P>> {
|
||||
impl XenClient {
|
||||
pub async fn new() -> Result<XenClient> {
|
||||
let store = XsdClient::open().await?;
|
||||
let call: XenCall = XenCall::open(current_domid)?;
|
||||
let domain_manager = BaseDomainManager::new(call.clone(), platform).await?;
|
||||
let call: XenCall = XenCall::open(0)?;
|
||||
let domain_manager = PlatformDomainManager::new(call.clone()).await?;
|
||||
Ok(XenClient {
|
||||
store,
|
||||
call,
|
||||
@ -125,29 +41,47 @@ impl<P: BootSetupPlatform> XenClient<P> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create(&self, config: &DomainConfig) -> Result<CreatedDomain> {
|
||||
let created = self.domain_manager.create(config.base.clone()).await?;
|
||||
match self.init(created.domid, config, &created).await {
|
||||
Ok(_) => Ok(created),
|
||||
pub async fn create(&self, config: DomainConfig) -> Result<DomainResult> {
|
||||
let platform = config
|
||||
.get_platform()
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("platform"))?
|
||||
.clone();
|
||||
let platform = self.domain_manager.create(platform).await?;
|
||||
match self.init(platform.domid, config, &platform).await {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => {
|
||||
// ignore since destroying a domain is best-effort when an error occurs
|
||||
let _ = self.domain_manager.destroy(created.domid).await;
|
||||
let _ = self.domain_manager.destroy(platform.domid).await;
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn transaction(&self, domid: u32, backend_domid: u32) -> Result<ClientTransaction> {
|
||||
ClientTransaction::new(&self.store, domid, backend_domid).await
|
||||
pub async fn transaction(&self, domid: u32, backend_domid: u32) -> Result<XenTransaction> {
|
||||
XenTransaction::new(&self.store, domid, backend_domid).await
|
||||
}
|
||||
|
||||
async fn init(&self, domid: u32, config: &DomainConfig, created: &CreatedDomain) -> Result<()> {
|
||||
async fn init(
|
||||
&self,
|
||||
domid: u32,
|
||||
mut config: DomainConfig,
|
||||
created: &PlatformDomainInfo,
|
||||
) -> Result<DomainResult> {
|
||||
trace!("xenclient init domid={} domain={:?}", domid, created);
|
||||
let transaction = self.transaction(domid, config.backend_domid).await?;
|
||||
transaction
|
||||
.add_domain_declaration(&config.name, &config.base, created)
|
||||
.await?;
|
||||
transaction.commit().await?;
|
||||
let platform_config = config
|
||||
.get_platform()
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("platform"))?;
|
||||
loop {
|
||||
let transaction = self.transaction(domid, config.get_backend_domid()).await?;
|
||||
transaction
|
||||
.add_domain_declaration(config.get_name().clone(), platform_config, created)
|
||||
.await?;
|
||||
if transaction.maybe_commit().await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !self
|
||||
.store
|
||||
.introduce_domain(domid, created.store_mfn, created.store_evtchn)
|
||||
@ -155,57 +89,69 @@ impl<P: BootSetupPlatform> XenClient<P> {
|
||||
{
|
||||
return Err(Error::IntroduceDomainFailed);
|
||||
}
|
||||
let transaction = self.transaction(domid, config.backend_domid).await?;
|
||||
transaction
|
||||
.add_channel_device(
|
||||
created,
|
||||
0,
|
||||
&DomainChannel {
|
||||
typ: config
|
||||
.swap_console_backend
|
||||
.clone()
|
||||
.unwrap_or("xenconsoled".to_string())
|
||||
.to_string(),
|
||||
initialized: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
config.prepare(domid, &self.call, created).await?;
|
||||
let mut channels;
|
||||
let mut vifs;
|
||||
let mut vbds;
|
||||
let mut fs9ps;
|
||||
let mut pci_result;
|
||||
loop {
|
||||
let transaction = self.transaction(domid, config.get_backend_domid()).await?;
|
||||
|
||||
for (index, channel) in config.channels.iter().enumerate() {
|
||||
transaction
|
||||
.add_channel_device(created, index + 1, channel)
|
||||
.await?;
|
||||
channels = Vec::new();
|
||||
for channel in config.get_channels() {
|
||||
let result = channel.add_to_transaction(&transaction).await?;
|
||||
channels.push(result);
|
||||
}
|
||||
|
||||
vifs = Vec::new();
|
||||
for vif in config.get_vifs() {
|
||||
let result = vif.add_to_transaction(&transaction).await?;
|
||||
vifs.push(result);
|
||||
}
|
||||
|
||||
vbds = Vec::new();
|
||||
for vbd in config.get_vbds() {
|
||||
let result = vbd.add_to_transaction(&transaction).await?;
|
||||
vbds.push(result);
|
||||
}
|
||||
|
||||
fs9ps = Vec::new();
|
||||
for fs9p in config.get_fs9ps() {
|
||||
let result = fs9p.add_to_transaction(&transaction).await?;
|
||||
fs9ps.push(result);
|
||||
}
|
||||
|
||||
pci_result = None;
|
||||
if let Some(pci) = config.get_pci().as_ref() {
|
||||
pci_result = Some(pci.add_to_transaction(&transaction).await?);
|
||||
}
|
||||
|
||||
for (key, value) in config.get_extra_keys() {
|
||||
transaction.write(key, value, None).await?;
|
||||
}
|
||||
|
||||
for rw_path in config.get_rw_paths() {
|
||||
transaction.add_rw_path(rw_path).await?;
|
||||
}
|
||||
|
||||
if transaction.maybe_commit().await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (index, disk) in config.disks.iter().enumerate() {
|
||||
transaction.add_vbd_device(index, disk).await?;
|
||||
if config.get_start() {
|
||||
self.call.unpause_domain(domid).await?;
|
||||
}
|
||||
|
||||
for (index, filesystem) in config.filesystems.iter().enumerate() {
|
||||
transaction.add_9pfs_device(index, filesystem).await?;
|
||||
}
|
||||
|
||||
for (index, vif) in config.vifs.iter().enumerate() {
|
||||
transaction.add_vif_device(index, vif).await?;
|
||||
}
|
||||
|
||||
for (index, pci) in config.pcis.iter().enumerate() {
|
||||
transaction
|
||||
.add_pci_device(&self.call, index, config.pcis.len(), pci)
|
||||
.await?;
|
||||
}
|
||||
|
||||
for (key, value) in &config.extra_keys {
|
||||
transaction.write_key(key, value).await?;
|
||||
}
|
||||
|
||||
for key in &config.extra_rw_paths {
|
||||
transaction.add_rw_path(key).await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
self.call.unpause_domain(domid).await?;
|
||||
Ok(())
|
||||
Ok(DomainResult {
|
||||
platform: created.clone(),
|
||||
channels,
|
||||
vifs,
|
||||
vbds,
|
||||
fs9ps,
|
||||
pci: pci_result,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn destroy(&self, domid: u32) -> Result<()> {
|
||||
@ -251,39 +197,7 @@ impl<P: BootSetupPlatform> XenClient<P> {
|
||||
}
|
||||
|
||||
for backend in &backend_paths {
|
||||
let state_path = format!("{}/state", backend);
|
||||
let mut watch = self.store.create_watch(&state_path).await?;
|
||||
let online_path = format!("{}/online", backend);
|
||||
let tx = self.store.transaction().await?;
|
||||
let state = tx.read_string(&state_path).await?.unwrap_or(String::new());
|
||||
if state.is_empty() {
|
||||
break;
|
||||
}
|
||||
tx.write_string(&online_path, "0").await?;
|
||||
if !state.is_empty() && u32::from_str(&state).unwrap_or(0) != 6 {
|
||||
tx.write_string(&state_path, "5").await?;
|
||||
}
|
||||
self.store.bind_watch(&watch).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let mut count: u32 = 0;
|
||||
loop {
|
||||
if count >= 3 {
|
||||
debug!("unable to safely destroy backend: {}", backend);
|
||||
break;
|
||||
}
|
||||
let _ = timeout(Duration::from_secs(1), watch.receiver.recv()).await;
|
||||
let state = self
|
||||
.store
|
||||
.read_string(&state_path)
|
||||
.await?
|
||||
.unwrap_or_else(|| "6".to_string());
|
||||
let state = i64::from_str(&state).unwrap_or(-1);
|
||||
if state == 6 {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
self.destroy_backend(backend).await?;
|
||||
}
|
||||
|
||||
let tx = self.store.transaction().await?;
|
||||
@ -305,4 +219,72 @@ impl<P: BootSetupPlatform> XenClient<P> {
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn destroy_backend(&self, backend: &str) -> Result<()> {
|
||||
let state_path = format!("{}/state", backend);
|
||||
let mut watch = self.store.create_watch(&state_path).await?;
|
||||
let online_path = format!("{}/online", backend);
|
||||
let tx = self.store.transaction().await?;
|
||||
let state = tx.read_string(&state_path).await?.unwrap_or(String::new());
|
||||
if state.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
tx.write_string(&online_path, "0").await?;
|
||||
if !state.is_empty() && u32::from_str(&state).unwrap_or(0) != 6 {
|
||||
tx.write_string(&state_path, "5").await?;
|
||||
}
|
||||
self.store.bind_watch(&watch).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let mut count: u32 = 0;
|
||||
loop {
|
||||
if count >= 3 {
|
||||
debug!("unable to safely destroy backend: {}", backend);
|
||||
break;
|
||||
}
|
||||
let _ = timeout(Duration::from_secs(1), watch.receiver.recv()).await;
|
||||
let state = self
|
||||
.store
|
||||
.read_string(&state_path)
|
||||
.await?
|
||||
.unwrap_or_else(|| "6".to_string());
|
||||
let state = i64::from_str(&state).unwrap_or(-1);
|
||||
if state == 6 {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
self.store.rm(backend).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn destroy_device(
|
||||
&self,
|
||||
category: &str,
|
||||
domid: u32,
|
||||
devid: u64,
|
||||
blkid: Option<u32>,
|
||||
) -> Result<()> {
|
||||
let dom_path = self.store.get_domain_path(domid).await?;
|
||||
let device_path = format!("{}/device/{}/{}", dom_path, category, devid);
|
||||
if let Some(backend_path) = self
|
||||
.store
|
||||
.read_string(format!("{}/backend", device_path).as_str())
|
||||
.await?
|
||||
{
|
||||
self.destroy_backend(&backend_path).await?;
|
||||
}
|
||||
self.destroy_backend(&device_path).await?;
|
||||
loop {
|
||||
let tx = self.transaction(domid, 0).await?;
|
||||
tx.release_devid(devid).await?;
|
||||
if let Some(blkid) = blkid {
|
||||
tx.release_blkid(blkid).await?;
|
||||
}
|
||||
if tx.maybe_commit().await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,582 +0,0 @@
|
||||
use indexmap::IndexMap;
|
||||
use xencall::{sys::DOMCTL_DEV_RDM_RELAXED, XenCall};
|
||||
use xenplatform::{
|
||||
domain::{BaseDomainConfig, CreatedDomain},
|
||||
sys::XEN_PAGE_SHIFT,
|
||||
};
|
||||
use xenstore::{
|
||||
XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
|
||||
XS_PERM_READ_WRITE,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
pci::XenPciBackend,
|
||||
DomainChannel, DomainDisk, DomainFilesystem, DomainNetworkInterface, DomainPciDevice,
|
||||
DomainPciRdmReservePolicy,
|
||||
};
|
||||
|
||||
pub struct ClientTransaction {
|
||||
tx: XsdTransaction,
|
||||
abort: bool,
|
||||
domid: u32,
|
||||
dom_path: String,
|
||||
backend_domid: u32,
|
||||
backend_dom_path: String,
|
||||
}
|
||||
|
||||
impl ClientTransaction {
|
||||
pub async fn new(store: &XsdClient, domid: u32, backend_domid: u32) -> Result<Self> {
|
||||
let backend_dom_path = store.get_domain_path(0).await?;
|
||||
let dom_path = store.get_domain_path(domid).await?;
|
||||
Ok(ClientTransaction {
|
||||
tx: store.transaction().await?,
|
||||
abort: true,
|
||||
domid,
|
||||
dom_path,
|
||||
backend_domid,
|
||||
backend_dom_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add_domain_declaration(
|
||||
&self,
|
||||
name: impl AsRef<str>,
|
||||
base: &BaseDomainConfig,
|
||||
created: &CreatedDomain,
|
||||
) -> Result<()> {
|
||||
let vm_path = format!("/vm/{}", base.uuid);
|
||||
let ro_perm = &[
|
||||
XsPermission {
|
||||
id: 0,
|
||||
perms: XS_PERM_NONE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
let no_perm = &[XsPermission {
|
||||
id: 0,
|
||||
perms: XS_PERM_NONE,
|
||||
}];
|
||||
|
||||
let rw_perm = &[XsPermission {
|
||||
id: self.domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
}];
|
||||
|
||||
self.tx.rm(&self.dom_path).await?;
|
||||
self.tx.mknod(&self.dom_path, ro_perm).await?;
|
||||
|
||||
self.tx.rm(&vm_path).await?;
|
||||
self.tx.mknod(&vm_path, ro_perm).await?;
|
||||
|
||||
self.tx.mknod(&vm_path, no_perm).await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/device", vm_path).as_str(), no_perm)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.write_string(format!("{}/vm", self.dom_path).as_str(), &vm_path)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.mknod(format!("{}/cpu", self.dom_path).as_str(), ro_perm)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/memory", self.dom_path).as_str(), ro_perm)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.mknod(format!("{}/control", self.dom_path).as_str(), ro_perm)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.mknod(
|
||||
format!("{}/control/shutdown", self.dom_path).as_str(),
|
||||
rw_perm,
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(
|
||||
format!("{}/control/feature-poweroff", self.dom_path).as_str(),
|
||||
rw_perm,
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(
|
||||
format!("{}/control/feature-reboot", self.dom_path).as_str(),
|
||||
rw_perm,
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(
|
||||
format!("{}/control/feature-suspend", self.dom_path).as_str(),
|
||||
rw_perm,
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/control/sysrq", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.mknod(format!("{}/data", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/drivers", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/feature", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/attr", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
self.tx
|
||||
.mknod(format!("{}/error", self.dom_path).as_str(), rw_perm)
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.write_string(format!("{}/uuid", vm_path).as_str(), &base.uuid.to_string())
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/name", self.dom_path).as_str(), name.as_ref())
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/name", vm_path).as_str(), name.as_ref())
|
||||
.await?;
|
||||
|
||||
self.tx
|
||||
.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/image/cmdline", vm_path).as_str(), &base.cmdline)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/memory/static-max", self.dom_path).as_str(),
|
||||
&(base.max_mem_mb * 1024).to_string(),
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/memory/target", self.dom_path).as_str(),
|
||||
&(base.target_mem_mb * 1024).to_string(),
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/memory/videoram", self.dom_path).as_str(), "0")
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/domid", self.dom_path).as_str(),
|
||||
&created.domid.to_string(),
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/type", self.dom_path).as_str(), "PV")
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/store/port", self.dom_path).as_str(),
|
||||
&created.store_evtchn.to_string(),
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/store/ring-ref", self.dom_path).as_str(),
|
||||
&created.store_mfn.to_string(),
|
||||
)
|
||||
.await?;
|
||||
for i in 0..base.max_vcpus {
|
||||
let path = format!("{}/cpu/{}", self.dom_path, i);
|
||||
self.tx.mkdir(&path).await?;
|
||||
self.tx.set_perms(&path, ro_perm).await?;
|
||||
let path = format!("{}/cpu/{}/availability", self.dom_path, i);
|
||||
self.tx
|
||||
.write_string(
|
||||
&path,
|
||||
if i < base.target_vcpus {
|
||||
"online"
|
||||
} else {
|
||||
"offline"
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.tx.set_perms(&path, ro_perm).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_key(&self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
|
||||
self.tx
|
||||
.write_string(
|
||||
&format!("{}/{}", self.dom_path, key.as_ref()),
|
||||
value.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
|
||||
let rw_perm = &[XsPermission {
|
||||
id: self.domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
}];
|
||||
|
||||
self.tx
|
||||
.mknod(&format!("{}/{}", self.dom_path, key.as_ref()), rw_perm)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_device(
|
||||
&self,
|
||||
typ: impl AsRef<str>,
|
||||
id: u64,
|
||||
frontend_items: Vec<(&str, String)>,
|
||||
backend_items: Vec<(&str, String)>,
|
||||
) -> Result<()> {
|
||||
let console_zero = typ.as_ref() == "console" && id == 0;
|
||||
|
||||
let frontend_path = if console_zero {
|
||||
format!("{}/console", self.dom_path)
|
||||
} else {
|
||||
format!("{}/device/{}/{}", self.dom_path, typ.as_ref(), id)
|
||||
};
|
||||
let backend_path = format!(
|
||||
"{}/backend/{}/{}/{}",
|
||||
self.backend_dom_path,
|
||||
typ.as_ref(),
|
||||
self.domid,
|
||||
id
|
||||
);
|
||||
|
||||
let mut backend_items: Vec<(&str, String)> = backend_items.clone();
|
||||
let mut frontend_items: Vec<(&str, String)> = frontend_items.clone();
|
||||
backend_items.push(("frontend", frontend_path.clone()));
|
||||
frontend_items.push(("backend", backend_path.clone()));
|
||||
let frontend_perms = &[
|
||||
XsPermission {
|
||||
id: self.domid,
|
||||
perms: XS_PERM_NONE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.backend_domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
let backend_perms = &[
|
||||
XsPermission {
|
||||
id: self.backend_domid,
|
||||
perms: XS_PERM_NONE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
self.tx.mknod(&frontend_path, frontend_perms).await?;
|
||||
for (p, value) in &frontend_items {
|
||||
let path = format!("{}/{}", frontend_path, *p);
|
||||
self.tx.write_string(&path, value).await?;
|
||||
if !console_zero {
|
||||
self.tx.set_perms(&path, frontend_perms).await?;
|
||||
}
|
||||
}
|
||||
self.tx.mknod(&backend_path, backend_perms).await?;
|
||||
for (p, value) in &backend_items {
|
||||
let path = format!("{}/{}", backend_path, *p);
|
||||
self.tx.write_string(&path, value).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_vbd_device(&self, index: usize, disk: &DomainDisk) -> Result<()> {
|
||||
let id = (202 << 8) | (index << 4) as u64;
|
||||
let backend_items: Vec<(&str, String)> = vec![
|
||||
("frontend-id", self.domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("removable", "0".to_string()),
|
||||
("bootable", "1".to_string()),
|
||||
("state", "1".to_string()),
|
||||
("dev", disk.vdev.to_string()),
|
||||
("type", "phy".to_string()),
|
||||
("mode", if disk.writable { "w" } else { "r" }.to_string()),
|
||||
("device-type", "disk".to_string()),
|
||||
("discard-enable", "0".to_string()),
|
||||
("specification", "xen".to_string()),
|
||||
("physical-device-path", disk.block.path.to_string()),
|
||||
(
|
||||
"physical-device",
|
||||
format!("{:02x}:{:02x}", disk.block.major, disk.block.minor),
|
||||
),
|
||||
];
|
||||
|
||||
let frontend_items: Vec<(&str, String)> = vec![
|
||||
("backend-id", self.backend_domid.to_string()),
|
||||
("state", "1".to_string()),
|
||||
("virtual-device", id.to_string()),
|
||||
("device-type", "disk".to_string()),
|
||||
("trusted", "1".to_string()),
|
||||
("protocol", "x86_64-abi".to_string()),
|
||||
];
|
||||
|
||||
self.add_device("vbd", id, frontend_items, backend_items)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_vif_device(&self, index: usize, vif: &DomainNetworkInterface) -> Result<()> {
|
||||
let id = 20 + index as u64;
|
||||
let mut backend_items: Vec<(&str, String)> = vec![
|
||||
("frontend-id", self.domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("state", "1".to_string()),
|
||||
("mac", vif.mac.to_string()),
|
||||
("mtu", vif.mtu.to_string()),
|
||||
("type", "vif".to_string()),
|
||||
("handle", id.to_string()),
|
||||
];
|
||||
|
||||
if vif.bridge.is_some() {
|
||||
backend_items.extend_from_slice(&[("bridge", vif.bridge.clone().unwrap())]);
|
||||
}
|
||||
|
||||
if vif.script.is_some() {
|
||||
backend_items.extend_from_slice(&[
|
||||
("script", vif.script.clone().unwrap()),
|
||||
("hotplug-status", "".to_string()),
|
||||
]);
|
||||
} else {
|
||||
backend_items.extend_from_slice(&[
|
||||
("script", "".to_string()),
|
||||
("hotplug-status", "connected".to_string()),
|
||||
]);
|
||||
}
|
||||
|
||||
let frontend_items: Vec<(&str, String)> = vec![
|
||||
("backend-id", self.backend_domid.to_string()),
|
||||
("state", "1".to_string()),
|
||||
("mac", vif.mac.to_string()),
|
||||
("trusted", "1".to_string()),
|
||||
("mtu", vif.mtu.to_string()),
|
||||
];
|
||||
|
||||
self.add_device("vif", id, frontend_items, backend_items)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_9pfs_device(&self, index: usize, filesystem: &DomainFilesystem) -> Result<()> {
|
||||
let id = 90 + index as u64;
|
||||
let backend_items: Vec<(&str, String)> = vec![
|
||||
("frontend-id", self.domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("state", "1".to_string()),
|
||||
("path", filesystem.path.to_string()),
|
||||
("security-model", "none".to_string()),
|
||||
];
|
||||
|
||||
let frontend_items: Vec<(&str, String)> = vec![
|
||||
("backend-id", self.backend_domid.to_string()),
|
||||
("state", "1".to_string()),
|
||||
("tag", filesystem.tag.to_string()),
|
||||
];
|
||||
|
||||
self.add_device("9pfs", id, frontend_items, backend_items)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_channel_device(
|
||||
&self,
|
||||
domain: &CreatedDomain,
|
||||
index: usize,
|
||||
channel: &DomainChannel,
|
||||
) -> Result<()> {
|
||||
let port = domain.console_evtchn;
|
||||
let ring = domain.console_mfn;
|
||||
|
||||
let mut backend_items = vec![
|
||||
("frontend-id", self.domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("protocol", "vt100".to_string()),
|
||||
];
|
||||
|
||||
let mut frontend_items = vec![
|
||||
("backend-id", self.backend_domid.to_string()),
|
||||
("limit", "1048576".to_string()),
|
||||
("output", "pty".to_string()),
|
||||
("tty", "".to_string()),
|
||||
];
|
||||
|
||||
frontend_items.push(("type", channel.typ.clone()));
|
||||
backend_items.push(("type", channel.typ.clone()));
|
||||
|
||||
if index == 0 {
|
||||
if channel.typ != "xenconsoled" {
|
||||
frontend_items.push(("state", "1".to_string()));
|
||||
}
|
||||
|
||||
frontend_items
|
||||
.extend_from_slice(&[("port", port.to_string()), ("ring-ref", ring.to_string())]);
|
||||
} else {
|
||||
frontend_items.extend_from_slice(&[
|
||||
("state", "1".to_string()),
|
||||
("protocol", "vt100".to_string()),
|
||||
]);
|
||||
}
|
||||
|
||||
if channel.initialized {
|
||||
backend_items.push(("state", "4".to_string()));
|
||||
} else {
|
||||
backend_items.push(("state", "1".to_string()));
|
||||
}
|
||||
|
||||
self.add_device("console", index as u64, frontend_items, backend_items)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_pci_device(
|
||||
&self,
|
||||
call: &XenCall,
|
||||
index: usize,
|
||||
device_count: usize,
|
||||
device: &DomainPciDevice,
|
||||
) -> Result<()> {
|
||||
let backend = XenPciBackend::new();
|
||||
if !backend.is_assigned(&device.bdf).await? {
|
||||
return Err(Error::PciDeviceNotAssignable(device.bdf));
|
||||
}
|
||||
let resources = backend.read_resources(&device.bdf).await?;
|
||||
for resource in resources {
|
||||
if resource.is_bar_io() {
|
||||
call.ioport_permission(
|
||||
self.domid,
|
||||
resource.start as u32,
|
||||
resource.size() as u32,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
call.iomem_permission(
|
||||
self.domid,
|
||||
resource.start >> XEN_PAGE_SHIFT,
|
||||
(resource.size() + (XEN_PAGE_SHIFT - 1)) >> XEN_PAGE_SHIFT,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(irq) = backend.read_irq(&device.bdf).await? {
|
||||
let irq = call.map_pirq(self.domid, irq as isize, None).await?;
|
||||
call.irq_permission(self.domid, irq, true).await?;
|
||||
}
|
||||
|
||||
backend.reset(&device.bdf).await?;
|
||||
|
||||
call.assign_device(
|
||||
self.domid,
|
||||
device.bdf.encode(),
|
||||
if device.rdm_reserve_policy == DomainPciRdmReservePolicy::Relaxed {
|
||||
DOMCTL_DEV_RDM_RELAXED
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if device.permissive {
|
||||
backend.enable_permissive(&device.bdf).await?;
|
||||
}
|
||||
|
||||
let id = 60;
|
||||
|
||||
if index == 0 {
|
||||
let backend_items: Vec<(&str, String)> = vec![
|
||||
("frontend-id", self.domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("state", "1".to_string()),
|
||||
("num_devs", device_count.to_string()),
|
||||
];
|
||||
|
||||
let frontend_items: Vec<(&str, String)> = vec![
|
||||
("backend-id", self.backend_domid.to_string()),
|
||||
("state", "1".to_string()),
|
||||
];
|
||||
|
||||
self.add_device("pci", id, frontend_items, backend_items)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let backend_path = format!(
|
||||
"{}/backend/{}/{}/{}",
|
||||
self.backend_dom_path, "pci", self.domid, id
|
||||
);
|
||||
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/key-{}", backend_path, index),
|
||||
&device.bdf.to_string(),
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/dev-{}", backend_path, index),
|
||||
&device.bdf.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(vdefn) = device.bdf.vdefn {
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/vdefn-{}", backend_path, index),
|
||||
&format!("{:#x}", vdefn),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let mut options = IndexMap::new();
|
||||
options.insert("permissive", if device.permissive { "1" } else { "0" });
|
||||
options.insert("rdm_policy", device.rdm_reserve_policy.to_option_str());
|
||||
options.insert("msitranslate", if device.msi_translate { "1" } else { "0" });
|
||||
options.insert(
|
||||
"power_mgmt",
|
||||
if device.power_management { "1" } else { "0" },
|
||||
);
|
||||
let options = options
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{}={}", key, value))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
self.tx
|
||||
.write_string(format!("{}/opts-{}", backend_path, index), &options)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn commit(mut self) -> Result<()> {
|
||||
self.abort = false;
|
||||
self.tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ClientTransaction {
|
||||
fn drop(&mut self) {
|
||||
if !self.abort {
|
||||
return;
|
||||
}
|
||||
let tx = self.tx.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let _ = tx.abort().await;
|
||||
});
|
||||
}
|
||||
}
|
89
crates/xen/xenclient/src/tx/channel.rs
Normal file
89
crates/xen/xenclient/src/tx/channel.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use xenplatform::domain::PlatformDomainInfo;
|
||||
|
||||
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct ChannelDeviceConfig {
|
||||
backend_type: String,
|
||||
default_console: bool,
|
||||
default_console_options: Option<(u32, u64)>,
|
||||
backend_initialized: bool,
|
||||
}
|
||||
|
||||
impl Default for ChannelDeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelDeviceConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
backend_type: "console".to_string(),
|
||||
default_console: false,
|
||||
default_console_options: None,
|
||||
backend_initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
|
||||
self.backend_type = backend_type.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_console(&mut self) -> &mut Self {
|
||||
self.default_console = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn backend_initialized(&mut self) -> &mut Self {
|
||||
self.backend_initialized = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn prepare(&mut self, platform: &PlatformDomainInfo) -> Result<()> {
|
||||
if self.default_console {
|
||||
self.default_console_options = Some((platform.console_evtchn, platform.console_mfn));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DeviceConfig for ChannelDeviceConfig {
|
||||
type Result = DeviceResult;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
|
||||
let id = tx.assign_next_devid().await?;
|
||||
let mut device = DeviceDescription::new("console", &self.backend_type);
|
||||
device
|
||||
.add_backend_bool("online", true)
|
||||
.add_backend_item("protocol", "vt100")
|
||||
.add_backend_item("type", &self.backend_type)
|
||||
.add_backend_item("state", if self.backend_initialized { 4 } else { 1 });
|
||||
|
||||
if self.default_console {
|
||||
device.special_frontend_path("console");
|
||||
let (port, ring_ref) = self
|
||||
.default_console_options
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("default_console_options"))?;
|
||||
device
|
||||
.add_frontend_item("port", port)
|
||||
.add_frontend_item("ring-ref", ring_ref);
|
||||
}
|
||||
|
||||
device
|
||||
.add_frontend_item("limit", 1048576)
|
||||
.add_frontend_item("output", "pty")
|
||||
.add_frontend_item("tty", "")
|
||||
.add_frontend_item("type", &self.backend_type)
|
||||
.add_frontend_item("state", 1);
|
||||
tx.add_device(id, device).await?;
|
||||
Ok(DeviceResult { id })
|
||||
}
|
||||
}
|
78
crates/xen/xenclient/src/tx/fs9p.rs
Normal file
78
crates/xen/xenclient/src/tx/fs9p.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct Fs9pDeviceConfig {
|
||||
backend_type: String,
|
||||
security_model: String,
|
||||
path: Option<String>,
|
||||
tag: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Fs9pDeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fs9pDeviceConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
backend_type: "9pfs".to_string(),
|
||||
security_model: "none".to_string(),
|
||||
path: None,
|
||||
tag: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
|
||||
self.backend_type = backend_type.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn security_model(&mut self, security_model: impl AsRef<str>) -> &mut Self {
|
||||
self.security_model = security_model.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn path(&mut self, path: impl AsRef<str>) -> &mut Self {
|
||||
self.path = Some(path.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(&mut self, tag: impl AsRef<str>) -> &mut Self {
|
||||
self.tag = Some(tag.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DeviceConfig for Fs9pDeviceConfig {
|
||||
type Result = DeviceResult;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
|
||||
let id = tx.assign_next_devid().await?;
|
||||
let path = self
|
||||
.path
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("path"))?;
|
||||
let tag = self
|
||||
.tag
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("tag"))?;
|
||||
let mut device = DeviceDescription::new("9pfs", &self.backend_type);
|
||||
device
|
||||
.add_backend_bool("online", true)
|
||||
.add_backend_item("state", 1)
|
||||
.add_backend_item("path", path)
|
||||
.add_backend_item("security_model", &self.security_model);
|
||||
device
|
||||
.add_frontend_item("state", 1)
|
||||
.add_frontend_item("tag", tag);
|
||||
tx.add_device(id, device).await?;
|
||||
Ok(DeviceResult { id })
|
||||
}
|
||||
}
|
425
crates/xen/xenclient/src/tx/mod.rs
Normal file
425
crates/xen/xenclient/src/tx/mod.rs
Normal file
@ -0,0 +1,425 @@
|
||||
pub mod channel;
|
||||
pub mod fs9p;
|
||||
pub mod pci;
|
||||
pub mod vbd;
|
||||
pub mod vif;
|
||||
|
||||
use crate::{
|
||||
devalloc::DeviceIdAllocator,
|
||||
error::{Error, Result},
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use xenplatform::domain::{PlatformDomainConfig, PlatformDomainInfo};
|
||||
use xenstore::{
|
||||
XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
|
||||
XS_PERM_READ_WRITE,
|
||||
};
|
||||
|
||||
pub struct XenTransaction {
|
||||
frontend_domid: u32,
|
||||
frontend_dom_path: String,
|
||||
backend_domid: u32,
|
||||
backend_dom_path: String,
|
||||
blkalloc: Arc<Mutex<DeviceIdAllocator>>,
|
||||
devalloc: Arc<Mutex<DeviceIdAllocator>>,
|
||||
tx: XsdTransaction,
|
||||
abort: bool,
|
||||
}
|
||||
|
||||
impl XenTransaction {
|
||||
pub async fn new(store: &XsdClient, frontend_domid: u32, backend_domid: u32) -> Result<Self> {
|
||||
let frontend_dom_path = store.get_domain_path(frontend_domid).await?;
|
||||
let backend_dom_path = store.get_domain_path(backend_domid).await?;
|
||||
let tx = store.transaction().await?;
|
||||
|
||||
let devalloc = XenTransaction::load_id_allocator(&tx, "devid", &frontend_dom_path).await?;
|
||||
let blkalloc = XenTransaction::load_id_allocator(&tx, "blkid", &frontend_dom_path).await?;
|
||||
|
||||
Ok(XenTransaction {
|
||||
frontend_domid,
|
||||
frontend_dom_path,
|
||||
backend_domid,
|
||||
backend_dom_path,
|
||||
tx,
|
||||
devalloc: Arc::new(Mutex::new(devalloc)),
|
||||
blkalloc: Arc::new(Mutex::new(blkalloc)),
|
||||
abort: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn load_id_allocator(
|
||||
tx: &XsdTransaction,
|
||||
allocator_type: &str,
|
||||
frontend_dom_path: &str,
|
||||
) -> Result<DeviceIdAllocator> {
|
||||
let state = tx
|
||||
.read(format!(
|
||||
"{}/{}-alloc-state",
|
||||
frontend_dom_path, allocator_type
|
||||
))
|
||||
.await?;
|
||||
let allocator = state
|
||||
.and_then(|state| DeviceIdAllocator::deserialize(&state))
|
||||
.unwrap_or_else(DeviceIdAllocator::new);
|
||||
Ok(allocator)
|
||||
}
|
||||
|
||||
pub async fn assign_next_devid(&self) -> Result<u64> {
|
||||
self.devalloc
|
||||
.lock()
|
||||
.await
|
||||
.allocate()
|
||||
.ok_or(Error::DevIdExhausted)
|
||||
.map(|x| x as u64)
|
||||
}
|
||||
|
||||
pub async fn assign_next_blkidx(&self) -> Result<u32> {
|
||||
self.blkalloc
|
||||
.lock()
|
||||
.await
|
||||
.allocate()
|
||||
.ok_or(Error::DevIdExhausted)
|
||||
}
|
||||
|
||||
pub async fn release_devid(&self, devid: u64) -> Result<()> {
|
||||
self.devalloc.lock().await.release(devid as u32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn release_blkid(&self, blkid: u32) -> Result<()> {
|
||||
self.blkalloc.lock().await.release(blkid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write(
|
||||
&self,
|
||||
key: impl AsRef<str>,
|
||||
value: impl AsRef<str>,
|
||||
perms: Option<&[XsPermission]>,
|
||||
) -> Result<()> {
|
||||
let path = format!("{}/{}", self.frontend_dom_path, key.as_ref());
|
||||
if let Some(perms) = perms {
|
||||
self.tx.mknod(&path, perms).await?;
|
||||
}
|
||||
|
||||
// empty string is written by mknod, if perms is set we can skip it.
|
||||
if perms.is_none() || perms.is_some() && !value.as_ref().is_empty() {
|
||||
self.tx.write_string(path, value.as_ref()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_domain_declaration(
|
||||
&self,
|
||||
name: Option<impl AsRef<str>>,
|
||||
platform: &PlatformDomainConfig,
|
||||
created: &PlatformDomainInfo,
|
||||
) -> Result<()> {
|
||||
let vm_path = format!("/vm/{}", platform.uuid);
|
||||
let ro_perm = &[
|
||||
XsPermission {
|
||||
id: 0,
|
||||
perms: XS_PERM_NONE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.frontend_domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
let no_perm = &[XsPermission {
|
||||
id: 0,
|
||||
perms: XS_PERM_NONE,
|
||||
}];
|
||||
|
||||
let rw_perm = &[XsPermission {
|
||||
id: self.frontend_domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
}];
|
||||
|
||||
self.tx.rm(&self.frontend_dom_path).await?;
|
||||
self.tx.mknod(&self.frontend_dom_path, ro_perm).await?;
|
||||
|
||||
self.tx.rm(&vm_path).await?;
|
||||
self.tx.mknod(&vm_path, no_perm).await?;
|
||||
self.tx
|
||||
.write_string(format!("{}/uuid", vm_path), &platform.uuid.to_string())
|
||||
.await?;
|
||||
|
||||
self.write("vm", &vm_path, None).await?;
|
||||
self.write("cpu", "", Some(ro_perm)).await?;
|
||||
self.write("memory", "", Some(ro_perm)).await?;
|
||||
self.write("control", "", Some(ro_perm)).await?;
|
||||
self.write("control/shutdown", "", Some(rw_perm)).await?;
|
||||
self.write("control/feature-poweroff", "", Some(rw_perm))
|
||||
.await?;
|
||||
self.write("control/feature-reboot", "", Some(rw_perm))
|
||||
.await?;
|
||||
self.write("control/feature-suspend", "", Some(rw_perm))
|
||||
.await?;
|
||||
self.write("control/sysrq", "", Some(rw_perm)).await?;
|
||||
self.write("data", "", Some(rw_perm)).await?;
|
||||
self.write("drivers", "", Some(rw_perm)).await?;
|
||||
self.write("feature", "", Some(rw_perm)).await?;
|
||||
self.write("attr", "", Some(rw_perm)).await?;
|
||||
self.write("error", "", Some(rw_perm)).await?;
|
||||
self.write("uuid", platform.uuid.to_string(), Some(ro_perm))
|
||||
.await?;
|
||||
if let Some(name) = name {
|
||||
self.write("name", name.as_ref(), Some(ro_perm)).await?;
|
||||
}
|
||||
self.write(
|
||||
"memory/static-max",
|
||||
(platform.resources.max_memory_mb * 1024).to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
self.write(
|
||||
"memory/target",
|
||||
(platform.resources.assigned_memory_mb * 1024).to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
self.write("memory/videoram", "0", None).await?;
|
||||
self.write("domid", self.frontend_domid.to_string(), None)
|
||||
.await?;
|
||||
self.write("type", "PV", None).await?;
|
||||
self.write("store/port", created.store_evtchn.to_string(), None)
|
||||
.await?;
|
||||
self.write("store/ring-ref", created.store_mfn.to_string(), None)
|
||||
.await?;
|
||||
for i in 0..platform.resources.max_vcpus {
|
||||
let path = format!("{}/cpu/{}", self.frontend_dom_path, i);
|
||||
self.tx.mkdir(&path).await?;
|
||||
self.tx.set_perms(&path, ro_perm).await?;
|
||||
let path = format!("{}/cpu/{}/availability", self.frontend_dom_path, i);
|
||||
self.tx
|
||||
.write_string(
|
||||
&path,
|
||||
if i < platform.resources.assigned_vcpus {
|
||||
"online"
|
||||
} else {
|
||||
"offline"
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.tx.set_perms(&path, ro_perm).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_device(&self, id: u64, device: DeviceDescription) -> Result<()> {
|
||||
let frontend_path = if let Some(ref special_frontend_path) = device.special_frontend_path {
|
||||
format!("{}/{}", self.frontend_dom_path, special_frontend_path)
|
||||
} else {
|
||||
format!(
|
||||
"{}/device/{}/{}",
|
||||
self.frontend_dom_path, device.frontend_type, id
|
||||
)
|
||||
};
|
||||
let backend_path = format!(
|
||||
"{}/backend/{}/{}/{}",
|
||||
self.backend_dom_path, device.backend_type, self.frontend_domid, id
|
||||
);
|
||||
|
||||
let frontend_perms = &[
|
||||
XsPermission {
|
||||
id: self.frontend_domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.backend_domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
let backend_perms = &[
|
||||
XsPermission {
|
||||
id: self.backend_domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
},
|
||||
XsPermission {
|
||||
id: self.frontend_domid,
|
||||
perms: XS_PERM_READ,
|
||||
},
|
||||
];
|
||||
|
||||
self.tx.mknod(&frontend_path, frontend_perms).await?;
|
||||
self.tx.mknod(&backend_path, backend_perms).await?;
|
||||
|
||||
for (key, value) in &device.backend_items {
|
||||
let path = format!("{}/{}", backend_path, key);
|
||||
self.tx.write_string(&path, value).await?;
|
||||
}
|
||||
|
||||
self.tx
|
||||
.write_string(format!("{}/frontend", backend_path), &frontend_path)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/frontend-id", backend_path),
|
||||
&self.frontend_domid.to_string(),
|
||||
)
|
||||
.await?;
|
||||
for (key, value) in &device.frontend_items {
|
||||
let path = format!("{}/{}", frontend_path, key);
|
||||
self.tx.write_string(&path, value).await?;
|
||||
if device.special_frontend_path.is_none() {
|
||||
self.tx.set_perms(&path, frontend_perms).await?;
|
||||
}
|
||||
}
|
||||
self.tx
|
||||
.write_string(format!("{}/backend", frontend_path), &backend_path)
|
||||
.await?;
|
||||
self.tx
|
||||
.write_string(
|
||||
format!("{}/backend-id", frontend_path),
|
||||
&self.backend_domid.to_string(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
|
||||
let rw_perm = &[XsPermission {
|
||||
id: self.frontend_domid,
|
||||
perms: XS_PERM_READ_WRITE,
|
||||
}];
|
||||
|
||||
self.tx
|
||||
.mknod(
|
||||
&format!("{}/{}", self.frontend_dom_path, key.as_ref()),
|
||||
rw_perm,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn before_commit(&self) -> Result<()> {
|
||||
let devid_allocator_state = self.devalloc.lock().await.serialize();
|
||||
let blkid_allocator_state = self.blkalloc.lock().await.serialize();
|
||||
self.tx
|
||||
.write(
|
||||
format!("{}/devid-alloc-state", self.frontend_dom_path),
|
||||
devid_allocator_state,
|
||||
)
|
||||
.await?;
|
||||
self.tx
|
||||
.write(
|
||||
format!("{}/blkid-alloc-state", self.frontend_dom_path),
|
||||
blkid_allocator_state,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn maybe_commit(mut self) -> Result<bool> {
|
||||
self.abort = false;
|
||||
self.before_commit().await?;
|
||||
Ok(self.tx.maybe_commit().await?)
|
||||
}
|
||||
|
||||
pub async fn commit(mut self) -> Result<()> {
|
||||
self.abort = false;
|
||||
self.before_commit().await?;
|
||||
self.tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XenTransaction {
|
||||
fn drop(&mut self) {
|
||||
if !self.abort {
|
||||
return;
|
||||
}
|
||||
let tx = self.tx.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let _ = tx.abort().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceDescription {
|
||||
frontend_type: String,
|
||||
backend_type: String,
|
||||
special_frontend_path: Option<String>,
|
||||
frontend_items: HashMap<String, String>,
|
||||
backend_items: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl DeviceDescription {
|
||||
pub fn new(frontend_type: impl AsRef<str>, backend_type: impl AsRef<str>) -> Self {
|
||||
Self {
|
||||
frontend_type: frontend_type.as_ref().to_string(),
|
||||
backend_type: backend_type.as_ref().to_string(),
|
||||
special_frontend_path: None,
|
||||
frontend_items: HashMap::new(),
|
||||
backend_items: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn special_frontend_path(&mut self, path: impl AsRef<str>) -> &mut Self {
|
||||
self.special_frontend_path = Some(path.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_frontend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
|
||||
self.frontend_items
|
||||
.insert(key.as_ref().to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_backend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
|
||||
self.backend_items
|
||||
.insert(key.as_ref().to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_frontend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
|
||||
self.add_frontend_item(key, if value { "1" } else { "0" })
|
||||
}
|
||||
|
||||
pub fn add_backend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
|
||||
self.add_backend_item(key, if value { "1" } else { "0" })
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeviceResult {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlockDeviceResult {
|
||||
pub id: u64,
|
||||
pub idx: u32,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait DeviceConfig {
|
||||
type Result;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<Self::Result>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlockDeviceRef {
|
||||
pub path: String,
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
}
|
||||
|
||||
impl BlockDeviceRef {
|
||||
pub fn new(path: impl AsRef<str>, major: u32, minor: u32) -> Self {
|
||||
Self {
|
||||
path: path.as_ref().to_string(),
|
||||
major,
|
||||
minor,
|
||||
}
|
||||
}
|
||||
}
|
194
crates/xen/xenclient/src/tx/pci.rs
Normal file
194
crates/xen/xenclient/src/tx/pci.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
pci::{PciBdf, XenPciBackend},
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use xencall::{sys::DOMCTL_DEV_RDM_RELAXED, XenCall};
|
||||
use xenplatform::sys::XEN_PAGE_SHIFT;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum PciRdmReservePolicy {
|
||||
Invalid,
|
||||
#[default]
|
||||
Strict,
|
||||
Relaxed,
|
||||
}
|
||||
|
||||
impl PciRdmReservePolicy {
|
||||
pub fn to_option_str(&self) -> &str {
|
||||
match self {
|
||||
PciRdmReservePolicy::Invalid => "-1",
|
||||
PciRdmReservePolicy::Strict => "0",
|
||||
PciRdmReservePolicy::Relaxed => "1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciDeviceConfig {
|
||||
bdf: PciBdf,
|
||||
rdm_reserve_policy: PciRdmReservePolicy,
|
||||
permissive: bool,
|
||||
msi_translate: bool,
|
||||
power_management: bool,
|
||||
}
|
||||
|
||||
pub struct PciRootDeviceConfig {
|
||||
backend_type: String,
|
||||
devices: Vec<PciDeviceConfig>,
|
||||
}
|
||||
|
||||
impl PciDeviceConfig {
|
||||
pub fn new(bdf: PciBdf) -> Self {
|
||||
Self {
|
||||
bdf,
|
||||
rdm_reserve_policy: PciRdmReservePolicy::Strict,
|
||||
permissive: false,
|
||||
msi_translate: false,
|
||||
power_management: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rdm_reserve_policy(&mut self, rdm_reserve_policy: PciRdmReservePolicy) -> &mut Self {
|
||||
self.rdm_reserve_policy = rdm_reserve_policy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn permissive(&mut self, permissive: bool) -> &mut Self {
|
||||
self.permissive = permissive;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn msi_translate(&mut self, msi_translate: bool) -> &mut Self {
|
||||
self.msi_translate = msi_translate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn power_management(&mut self, power_management: bool) -> &mut Self {
|
||||
self.power_management = power_management;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciRootDeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PciRootDeviceConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
backend_type: "pci".to_string(),
|
||||
devices: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
|
||||
self.backend_type = backend_type.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_device(&mut self, device: PciDeviceConfig) -> &mut Self {
|
||||
self.devices.push(device);
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn prepare(&self, domid: u32, call: &XenCall) -> Result<()> {
|
||||
for device in &self.devices {
|
||||
let backend = XenPciBackend::new();
|
||||
if !backend.is_assigned(&device.bdf).await? {
|
||||
return Err(Error::PciDeviceNotAssignable(device.bdf));
|
||||
}
|
||||
let resources = backend.read_resources(&device.bdf).await?;
|
||||
for resource in resources {
|
||||
if resource.is_bar_io() {
|
||||
call.ioport_permission(
|
||||
domid,
|
||||
resource.start as u32,
|
||||
resource.size() as u32,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
call.iomem_permission(
|
||||
domid,
|
||||
resource.start >> XEN_PAGE_SHIFT,
|
||||
(resource.size() + (XEN_PAGE_SHIFT - 1)) >> XEN_PAGE_SHIFT,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(irq) = backend.read_irq(&device.bdf).await? {
|
||||
let irq = call.map_pirq(domid, irq as isize, None).await?;
|
||||
call.irq_permission(domid, irq, true).await?;
|
||||
}
|
||||
|
||||
backend.reset(&device.bdf).await?;
|
||||
|
||||
call.assign_device(
|
||||
domid,
|
||||
device.bdf.encode(),
|
||||
if device.rdm_reserve_policy == PciRdmReservePolicy::Relaxed {
|
||||
DOMCTL_DEV_RDM_RELAXED
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if device.permissive {
|
||||
backend.enable_permissive(&device.bdf).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DeviceConfig for PciRootDeviceConfig {
|
||||
type Result = DeviceResult;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
|
||||
let id = tx.assign_next_devid().await?;
|
||||
let mut device = DeviceDescription::new("pci", &self.backend_type);
|
||||
device
|
||||
.add_backend_bool("online", true)
|
||||
.add_backend_item("state", 1)
|
||||
.add_backend_item("num_devs", self.devices.len());
|
||||
|
||||
for (index, pci) in self.devices.iter().enumerate() {
|
||||
let mut options = IndexMap::new();
|
||||
options.insert("permissive", if pci.permissive { "1" } else { "0" });
|
||||
options.insert("rdm_policy", pci.rdm_reserve_policy.to_option_str());
|
||||
options.insert("msitranslate", if pci.msi_translate { "1" } else { "0" });
|
||||
let options = options
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{}={}", key, value))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
device
|
||||
.add_backend_item(format!("key-{}", index), pci.bdf.to_string())
|
||||
.add_backend_item(format!("dev-{}", index), pci.bdf.to_string())
|
||||
.add_backend_item(format!("opts-{}", index), options);
|
||||
|
||||
if let Some(vdefn) = pci.bdf.vdefn {
|
||||
device.add_backend_item(format!("vdefn-{}", index), format!("{:#x}", vdefn));
|
||||
}
|
||||
}
|
||||
|
||||
device.add_frontend_item("state", 1);
|
||||
tx.add_device(id, device).await?;
|
||||
Ok(DeviceResult { id })
|
||||
}
|
||||
}
|
131
crates/xen/xenclient/src/tx/vbd.rs
Normal file
131
crates/xen/xenclient/src/tx/vbd.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use super::{BlockDeviceRef, BlockDeviceResult, DeviceConfig, DeviceDescription, XenTransaction};
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
util::vbd_blkidx_to_disk_name,
|
||||
};
|
||||
|
||||
pub struct VbdDeviceConfig {
|
||||
backend_type: String,
|
||||
removable: bool,
|
||||
bootable: bool,
|
||||
writable: bool,
|
||||
discard: bool,
|
||||
trusted: bool,
|
||||
block_device: Option<BlockDeviceRef>,
|
||||
}
|
||||
|
||||
impl Default for VbdDeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl VbdDeviceConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
backend_type: "vbd".to_string(),
|
||||
removable: false,
|
||||
bootable: true,
|
||||
writable: false,
|
||||
discard: false,
|
||||
trusted: true,
|
||||
block_device: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
|
||||
self.backend_type = backend_type.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn removable(&mut self, removable: bool) -> &mut Self {
|
||||
self.removable = removable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bootable(&mut self, bootable: bool) -> &mut Self {
|
||||
self.bootable = bootable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn writable(&mut self, writable: bool) -> &mut Self {
|
||||
self.writable = writable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn discard(&mut self, discard: bool) -> &mut Self {
|
||||
self.discard = discard;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trusted(&mut self, trusted: bool) -> &mut Self {
|
||||
self.trusted = trusted;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block_device(&mut self, block_device: BlockDeviceRef) -> &mut Self {
|
||||
self.block_device = Some(block_device);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DeviceConfig for VbdDeviceConfig {
|
||||
type Result = BlockDeviceResult;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<BlockDeviceResult> {
|
||||
let id = tx.assign_next_devid().await?;
|
||||
let idx = tx.assign_next_blkidx().await?;
|
||||
let vdev = vbd_blkidx_to_disk_name(idx)?;
|
||||
let block_device = self
|
||||
.block_device
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("block device"))?;
|
||||
|
||||
let mut device = DeviceDescription::new("vbd", &self.backend_type);
|
||||
device
|
||||
.add_backend_item("online", 1)
|
||||
.add_backend_bool("removable", self.removable)
|
||||
.add_backend_bool("bootable", self.bootable)
|
||||
.add_backend_item("type", "phy")
|
||||
.add_backend_item("device-type", "disk")
|
||||
.add_backend_item("discard-enable", self.discard)
|
||||
.add_backend_item("specification", "xen")
|
||||
.add_backend_item("physical-device-path", &block_device.path)
|
||||
.add_backend_item("mode", if self.writable { "w" } else { "r" })
|
||||
.add_backend_item(
|
||||
"physical-device",
|
||||
format!("{:02x}:{:02x}", block_device.major, block_device.minor),
|
||||
)
|
||||
.add_backend_item("dev", &vdev)
|
||||
.add_backend_item("state", 1);
|
||||
|
||||
// we should use standard virtual-device support for first few block devices.
|
||||
// the kernel warns when you use ext for indexes 5 or less, due to
|
||||
// potential id overlapping.
|
||||
let (vdev, vd_key) = if idx <= 5 {
|
||||
// shift by 4 as partition count is 16
|
||||
((202 << 8) | (idx as u64 * 16u64), "virtual-device")
|
||||
} else {
|
||||
// this is silly but 256 is the number of partitions
|
||||
// multiply the index by that to get the actual id
|
||||
((1u64 << 28u64) + (idx as u64) * 256, "virtual-device-ext")
|
||||
};
|
||||
|
||||
device
|
||||
.add_frontend_item(vd_key, vdev)
|
||||
.add_frontend_item("state", 1)
|
||||
.add_frontend_item("device-type", "disk")
|
||||
.add_frontend_bool("trusted", self.trusted)
|
||||
.add_frontend_item("protocol", "x86_64-abi")
|
||||
.add_frontend_item("x-index", idx);
|
||||
|
||||
tx.add_device(id, device).await?;
|
||||
|
||||
Ok(BlockDeviceResult { id, idx })
|
||||
}
|
||||
}
|
112
crates/xen/xenclient/src/tx/vif.rs
Normal file
112
crates/xen/xenclient/src/tx/vif.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub struct VifDeviceConfig {
|
||||
backend_type: String,
|
||||
mac: Option<String>,
|
||||
mtu: Option<u32>,
|
||||
script: Option<String>,
|
||||
bridge: Option<String>,
|
||||
trusted: bool,
|
||||
}
|
||||
|
||||
impl Default for VifDeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl VifDeviceConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
backend_type: "vif".to_string(),
|
||||
mac: None,
|
||||
mtu: None,
|
||||
script: None,
|
||||
bridge: None,
|
||||
trusted: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
|
||||
self.backend_type = backend_type.as_ref().to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mac(&mut self, mac: impl AsRef<str>) -> &mut Self {
|
||||
self.mac = Some(mac.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mtu(&mut self, mtu: u32) -> &mut Self {
|
||||
self.mtu = Some(mtu);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn script(&mut self, script: impl AsRef<str>) -> &mut Self {
|
||||
self.script = Some(script.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bridge(&mut self, bridge: impl AsRef<str>) -> &mut Self {
|
||||
self.bridge = Some(bridge.as_ref().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trusted(&mut self, trusted: bool) -> &mut Self {
|
||||
self.trusted = trusted;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn done(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DeviceConfig for VifDeviceConfig {
|
||||
type Result = DeviceResult;
|
||||
|
||||
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
|
||||
let id = tx.assign_next_devid().await?;
|
||||
let mac = self
|
||||
.mac
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::ParameterMissing("mac address"))?;
|
||||
let mtu = self
|
||||
.mtu
|
||||
.ok_or_else(|| Error::ParameterMissing("mtu"))?
|
||||
.to_string();
|
||||
let mut device = DeviceDescription::new("vif", &self.backend_type);
|
||||
device
|
||||
.add_backend_item("online", 1)
|
||||
.add_backend_item("state", 1)
|
||||
.add_backend_item("mac", mac)
|
||||
.add_backend_item("mtu", &mtu)
|
||||
.add_backend_item("type", "vif")
|
||||
.add_backend_item("handle", id);
|
||||
|
||||
if let Some(bridge) = self.bridge.as_ref() {
|
||||
device.add_backend_item("bridge", bridge);
|
||||
}
|
||||
|
||||
if let Some(script) = self.script.as_ref() {
|
||||
device
|
||||
.add_backend_item("script", script)
|
||||
.add_backend_item("hotplug-status", "");
|
||||
} else {
|
||||
device
|
||||
.add_backend_item("script", "")
|
||||
.add_backend_item("hotplug-status", "connected");
|
||||
}
|
||||
|
||||
device
|
||||
.add_frontend_item("state", 1)
|
||||
.add_frontend_item("mac", mac)
|
||||
.add_frontend_item("mtu", &mtu)
|
||||
.add_frontend_bool("trusted", self.trusted);
|
||||
|
||||
tx.add_device(id, device.done()).await?;
|
||||
Ok(DeviceResult { id })
|
||||
}
|
||||
}
|
21
crates/xen/xenclient/src/util.rs
Normal file
21
crates/xen/xenclient/src/util.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub fn vbd_blkidx_to_disk_name(blkid: u32) -> Result<String> {
|
||||
let mut name = "xvd".to_string();
|
||||
let mut suffix = String::new();
|
||||
let mut n = blkid;
|
||||
loop {
|
||||
let c = (n % 26) as u8;
|
||||
let c = b'a' + c;
|
||||
let c = char::from_u32(c as u32).ok_or(Error::InvalidBlockIdx)?;
|
||||
suffix.push(c);
|
||||
if n >= 26 {
|
||||
n = (n / 26) - 1;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
name.push_str(&suffix.chars().rev().collect::<String>());
|
||||
Ok(name)
|
||||
}
|
Reference in New Issue
Block a user