oh my god, we have a console

This commit is contained in:
Alex Zenla
2024-01-17 05:22:47 -08:00
parent 6ca61410ad
commit 15ba27e573
14 changed files with 643 additions and 559 deletions

View File

@ -32,10 +32,6 @@ env_logger = "0.10.1"
[lib]
path = "src/lib.rs"
[[example]]
name = "xenclient-simple"
path = "examples/simple.rs"
[[example]]
name = "xenclient-boot"
path = "examples/boot.rs"

View File

@ -1,13 +1,6 @@
use std::fs::read;
use std::{env, process};
use xencall::domctl::DomainControl;
use xencall::memory::MemoryControl;
use xencall::sys::CreateDomain;
use xencall::XenCall;
use xenclient::boot::BootSetup;
use xenclient::elfloader::ElfImageLoader;
use xenclient::XenClientError;
use xenevtchn::EventChannel;
use xenclient::create::DomainConfig;
use xenclient::{XenClient, XenClientError};
fn main() -> Result<(), XenClientError> {
env_logger::init();
@ -19,40 +12,15 @@ fn main() -> Result<(), XenClientError> {
}
let kernel_image_path = args.get(1).expect("argument not specified");
let initrd_path = args.get(2).expect("argument not specified");
let call = XenCall::open()?;
let domctl = DomainControl::new(&call);
let domain = CreateDomain {
let mut client = XenClient::open()?;
let config = DomainConfig {
max_vcpus: 1,
..Default::default()
mem_mb: 512,
kernel_path: kernel_image_path.to_string(),
initrd_path: initrd_path.to_string(),
cmdline: "debug elevator=noop".to_string(),
};
let domid = domctl.create_domain(domain)?;
boot(
domid,
kernel_image_path.as_str(),
initrd_path.as_str(),
&call,
&domctl,
)?;
Ok(())
}
fn boot(
domid: u32,
kernel_image_path: &str,
initrd_path: &str,
call: &XenCall,
domctl: &DomainControl,
) -> Result<(), XenClientError> {
println!("domain created: {:?}", domid);
let image_loader = ElfImageLoader::load_file_kernel(kernel_image_path)?;
let memctl = MemoryControl::new(call);
let mut boot = BootSetup::new(call, domctl, &memctl, domid);
let initrd = read(initrd_path)?;
let mut state = boot.initialize(&image_loader, initrd.as_slice(), 1, 512)?;
boot.boot(&mut state, "debug elevator=noop")?;
domctl.unpause_domain(domid)?;
let _evtchn = EventChannel::open()?;
let domid = client.create(config)?;
println!("created domain {}", domid);
Ok(())
}

View File

@ -1,18 +0,0 @@
use xenclient::create::{DomainConfig, PvDomainConfig};
use xenclient::{XenClient, XenClientError};
fn main() -> Result<(), XenClientError> {
env_logger::init();
let mut client = XenClient::open()?;
let mut config = DomainConfig::new();
config.configure_cpus(1);
config.configure_memory(524288, 524288, 0);
config.configure_pv(PvDomainConfig::new(
"/boot/vmlinuz-6.1.0-17-amd64".to_string(),
None,
None,
));
client.create(config)?;
Ok(())
}

View File

@ -16,8 +16,6 @@ use std::cmp::{max, min};
use std::ffi::c_void;
use std::mem::size_of;
use std::slice;
use xencall::domctl::DomainControl;
use xencall::memory::MemoryControl;
use xencall::sys::{VcpuGuestContext, MMUEXT_PIN_L4_TABLE};
use xencall::XenCall;
@ -41,9 +39,8 @@ pub struct BootImageInfo {
}
pub struct BootSetup<'a> {
domctl: &'a DomainControl<'a>,
memctl: &'a MemoryControl<'a>,
phys: PhysicalPages<'a>,
call: &'a XenCall,
pub phys: PhysicalPages<'a>,
domid: u32,
virt_alloc_end: u64,
pfn_alloc_end: u64,
@ -55,7 +52,7 @@ pub struct BootSetup<'a> {
pub struct DomainSegment {
vstart: u64,
_vend: u64,
pfn: u64,
pub pfn: u64,
addr: u64,
size: u64,
pages: u64,
@ -87,15 +84,9 @@ pub struct BootState {
}
impl BootSetup<'_> {
pub fn new<'a>(
call: &'a XenCall,
domctl: &'a DomainControl<'a>,
memctl: &'a MemoryControl<'a>,
domid: u32,
) -> BootSetup<'a> {
pub fn new(call: &XenCall, domid: u32) -> BootSetup {
BootSetup {
domctl,
memctl,
call,
phys: PhysicalPages::new(call, domid),
domid,
virt_alloc_end: 0,
@ -106,7 +97,7 @@ impl BootSetup<'_> {
}
fn initialize_memory(&mut self, total_pages: u64) -> Result<(), XenClientError> {
self.domctl.set_address_size(self.domid, 64)?;
self.call.set_address_size(self.domid, 64)?;
let mut vmemranges: Vec<VmemRange> = Vec::new();
let stub = VmemRange {
@ -162,7 +153,7 @@ impl BootSetup<'_> {
}
let extents_init_slice = extents_init.as_slice();
let extents = self.memctl.populate_physmap(
let extents = self.call.populate_physmap(
self.domid,
count,
SUPERPAGE_2MB_SHIFT as u32,
@ -191,7 +182,7 @@ impl BootSetup<'_> {
let p2m_end_idx = p2m_idx + allocsz as usize;
let input_extent_starts = &p2m[p2m_idx..p2m_end_idx];
let result =
self.memctl
self.call
.populate_physmap(self.domid, allocsz, 0, 0, input_extent_starts)?;
if result.len() != allocsz as usize {
@ -226,7 +217,7 @@ impl BootSetup<'_> {
let pfn = (image_info.virt_hypercall - image_info.virt_base) >> X86_PAGE_SHIFT;
let mfn = self.phys.p2m[pfn as usize];
self.domctl.hypercall_init(self.domid, mfn)?;
self.call.hypercall_init(self.domid, mfn)?;
Ok(())
}
@ -241,8 +232,6 @@ impl BootSetup<'_> {
"BootSetup initialize max_vcpus={:?} mem_mb={:?}",
max_vcpus, mem_mb
);
self.domctl.set_max_vcpus(self.domid, max_vcpus)?;
self.domctl.set_max_mem(self.domid, mem_mb * 1024)?;
let total_pages = mem_mb << (20 - X86_PAGE_SHIFT);
self.initialize_memory(total_pages)?;
@ -284,8 +273,8 @@ impl BootSetup<'_> {
}
let initrd_segment = initrd_segment.unwrap();
let store_evtchn = self.domctl.call.evtchn_alloc_unbound(self.domid, 0)?;
let console_evtchn = self.domctl.call.evtchn_alloc_unbound(self.domid, 0)?;
let store_evtchn = self.call.evtchn_alloc_unbound(self.domid, 0)?;
let console_evtchn = self.call.evtchn_alloc_unbound(self.domid, 0)?;
let state = BootState {
kernel_segment,
start_info_segment,
@ -306,7 +295,7 @@ impl BootSetup<'_> {
}
pub fn boot(&mut self, state: &mut BootState, cmdline: &str) -> Result<(), XenClientError> {
let domain_info = self.domctl.get_domain_info(self.domid)?;
let domain_info = self.call.get_domain_info(self.domid)?;
let shared_info_frame = domain_info.shared_info_frame;
state.shared_info_frame = shared_info_frame;
self.setup_page_tables(state)?;
@ -317,7 +306,7 @@ impl BootSetup<'_> {
self.phys.unmap(pg_pfn)?;
self.phys.unmap(state.p2m_segment.pfn)?;
let pg_mfn = self.phys.p2m[pg_pfn as usize];
self.memctl
self.call
.mmuext(self.domid, MMUEXT_PIN_L4_TABLE, pg_mfn, 0)?;
self.setup_shared_info(state.shared_info_frame)?;
@ -346,7 +335,7 @@ impl BootSetup<'_> {
vcpu.kernel_ss = vcpu.user_regs.ss as u64;
vcpu.kernel_sp = vcpu.user_regs.rsp;
debug!("vcpu context: {:?}", vcpu);
self.domctl.set_vcpu_context(self.domid, 0, &vcpu)?;
self.call.set_vcpu_context(self.domid, 0, &vcpu)?;
self.phys.unmap_all()?;
self.gnttab_seed(state)?;
Ok(())
@ -356,13 +345,10 @@ impl BootSetup<'_> {
let console_gfn = self.phys.p2m[state.console_segment.pfn as usize];
let xenstore_gfn = self.phys.p2m[state.xenstore_segment.pfn as usize];
let addr = self
.domctl
.call
.mmap(0, 1 << XEN_PAGE_SHIFT)
.ok_or(XenClientError::new("failed to mmap for resource"))?;
self.domctl
.call
.map_resource(self.domid, 1, 0, 0, 1, addr)?;
self.call.map_resource(self.domid, 1, 0, 0, 1, addr)?;
let entries = unsafe { slice::from_raw_parts_mut(addr as *mut GrantEntry, 2) };
entries[0].flags = 1 << 0;
entries[0].domid = 0;

View File

@ -1,19 +1,27 @@
use std::collections::HashMap;
pub struct DomainConfig {
vm_entries: HashMap<String, String>,
domain_entries: HashMap<String, String>,
pub max_vcpus: u32,
pub mem_mb: u64,
pub kernel_path: String,
pub initrd_path: String,
pub cmdline: String,
}
pub struct PvDomainConfig {
pub struct PvDomainStore {
kernel: String,
ramdisk: Option<String>,
cmdline: Option<String>,
}
impl DomainConfig {
pub fn new() -> DomainConfig {
DomainConfig {
pub struct DomainStore {
vm_entries: HashMap<String, String>,
domain_entries: HashMap<String, String>,
}
impl DomainStore {
pub fn new() -> DomainStore {
DomainStore {
vm_entries: HashMap::new(),
domain_entries: HashMap::new(),
}
@ -43,7 +51,7 @@ impl DomainConfig {
pub fn configure_cpus(&mut self, _maxvcpus: u32) {}
pub fn configure_pv(&mut self, pv: PvDomainConfig) {
pub fn configure_pv(&mut self, pv: PvDomainStore) {
self.put_vm_str("image/ostype", "linux");
self.put_vm("image/kernel", pv.kernel);
@ -67,15 +75,15 @@ impl DomainConfig {
}
}
impl Default for DomainConfig {
impl Default for DomainStore {
fn default() -> Self {
DomainConfig::new()
DomainStore::new()
}
}
impl PvDomainConfig {
pub fn new(kernel: String, ramdisk: Option<String>, cmdline: Option<String>) -> PvDomainConfig {
PvDomainConfig {
impl PvDomainStore {
pub fn new(kernel: String, ramdisk: Option<String>, cmdline: Option<String>) -> PvDomainStore {
PvDomainStore {
kernel,
ramdisk,
cmdline,

View File

@ -5,16 +5,19 @@ pub mod mem;
pub mod sys;
mod x86;
use crate::boot::BootSetup;
use crate::create::DomainConfig;
use crate::elfloader::ElfImageLoader;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs::read;
use std::string::FromUtf8Error;
use xencall::domctl::DomainControl;
use uuid::Uuid;
use xencall::sys::CreateDomain;
use xencall::{XenCall, XenCallError};
use xenevtchn::EventChannelError;
use xenstore::bus::XsdBusError;
use xenstore::client::{XsdClient, XsdInterface};
use xenstore::client::{XsPermissions, XsdClient, XsdInterface};
pub struct XenClient {
store: XsdClient,
@ -83,29 +86,208 @@ impl XenClient {
Ok(XenClient { store, call })
}
pub fn create(&mut self, config: DomainConfig) -> Result<(), XenClientError> {
let domctl = DomainControl::new(&self.call);
let domid = domctl.create_domain(CreateDomain::default())?;
let domain = self.store.get_domain_path(domid)?;
let vm = self.store.read_string(format!("{}/vm", domain).as_str())?;
pub fn create(&mut self, config: DomainConfig) -> Result<u32, XenClientError> {
let domain = CreateDomain {
max_vcpus: config.max_vcpus,
..Default::default()
};
let domid = self.call.create_domain(domain)?;
let dom_path = self.store.get_domain_path(domid)?;
let uuid_string = Uuid::from_bytes(domain.handle).to_string();
let vm_path = format!("/vm/{}", uuid_string);
let libxl_path = format!("/libxl/{}", domid);
let ro_perm = XsPermissions { id: 0, perms: 0 };
let rw_perm = XsPermissions { id: 0, perms: 0 };
let no_perm = XsPermissions { id: 0, perms: 0 };
{
let mut tx = self.store.transaction()?;
tx.rm(dom_path.as_str())?;
tx.mknod(dom_path.as_str(), &ro_perm)?;
tx.rm(vm_path.as_str())?;
tx.mknod(vm_path.as_str(), &ro_perm)?;
tx.rm(libxl_path.as_str())?;
tx.mknod(vm_path.as_str(), &no_perm)?;
tx.mknod(format!("{}/device", vm_path).as_str(), &no_perm)?;
tx.write_string(format!("{}/vm", dom_path).as_str(), &vm_path)?;
tx.mknod(format!("{}/cpu", dom_path).as_str(), &ro_perm)?;
tx.mknod(format!("{}/memory", dom_path).as_str(), &ro_perm)?;
tx.mknod(format!("{}/control", dom_path).as_str(), &ro_perm)?;
tx.mknod(format!("{}/control/shutdown", dom_path).as_str(), &rw_perm)?;
tx.mknod(
format!("{}/control/feature-poweroff", dom_path).as_str(),
&rw_perm,
)?;
tx.mknod(
format!("{}/control/feature-reboot", dom_path).as_str(),
&rw_perm,
)?;
tx.mknod(
format!("{}/control/feature-suspend", dom_path).as_str(),
&rw_perm,
)?;
tx.mknod(format!("{}/control/sysrq", dom_path).as_str(), &rw_perm)?;
tx.mknod(format!("{}/data", dom_path).as_str(), &rw_perm)?;
tx.mknod(format!("{}/drivers", dom_path).as_str(), &rw_perm)?;
tx.mknod(format!("{}/feature", dom_path).as_str(), &rw_perm)?;
tx.mknod(format!("{}/attr", dom_path).as_str(), &rw_perm)?;
tx.mknod(format!("{}/error", dom_path).as_str(), &rw_perm)?;
tx.write_string(
format!("{}/uuid", vm_path).as_str(),
&Uuid::from_bytes(domain.handle).to_string(),
)?;
tx.write_string(
format!("{}/name", vm_path).as_str(),
"mycelium",
)?;
tx.write_string(format!("{}/type", libxl_path).as_str(), "pv")?;
tx.commit()?;
}
self.call.set_max_vcpus(domid, config.max_vcpus)?;
self.call.set_max_mem(domid, config.mem_mb * 1024)?;
let image_loader = ElfImageLoader::load_file_kernel(config.kernel_path.as_str())?;
let console_evtchn: u32;
let xenstore_evtchn: u32;
let console_mfn: u64;
let xenstore_mfn: u64;
{
let mut boot = BootSetup::new(&self.call, domid);
let initrd = read(config.initrd_path.as_str())?;
let mut state = boot.initialize(
&image_loader,
initrd.as_slice(),
config.max_vcpus,
config.mem_mb,
)?;
boot.boot(&mut state, config.cmdline.as_str())?;
console_evtchn = state.console_evtchn;
xenstore_evtchn = state.store_evtchn;
console_mfn = boot.phys.p2m[state.console_segment.pfn as usize];
xenstore_mfn = boot.phys.p2m[state.xenstore_segment.pfn as usize];
}
{
let mut tx = self.store.transaction()?;
tx.write_string(
format!("{}/image/os_type", vm_path).as_str(),
"linux",
)?;
tx.write_string(
format!("{}/image/kernel", vm_path).as_str(),
&config.kernel_path,
)?;
tx.write_string(
format!("{}/image/ramdisk", vm_path).as_str(),
&config.initrd_path,
)?;
tx.write_string(
format!("{}/image/cmdline", vm_path).as_str(),
&config.cmdline,
)?;
tx.write_string(
format!("{}/memory/static-max", dom_path).as_str(),
&(config.mem_mb * 1024).to_string(),
)?;
tx.write_string(
format!("{}/memory/target", dom_path).as_str(),
&(config.mem_mb * 1024).to_string(),
)?;
tx.write_string(
format!("{}/memory/videoram", dom_path).as_str(),
"0",
)?;
tx.write_string(format!("{}/domid", dom_path).as_str(), &domid.to_string())?;
tx.write_string(
format!("{}/store/port", dom_path).as_str(),
&xenstore_evtchn.to_string(),
)?;
tx.write_string(
format!("{}/store/ring-ref", dom_path).as_str(),
&xenstore_mfn.to_string(),
)?;
for i in 0..config.max_vcpus {
tx.write_string(
format!("{}/cpu/{}/availability", dom_path, i).as_str(),
"online",
)?;
}
tx.commit()?;
}
self.console_device_add(&dom_path.to_string(), domid, console_evtchn, console_mfn)?;
self.store
.introduce_domain(domid, xenstore_mfn, xenstore_evtchn)?;
self.call.unpause_domain(domid)?;
Ok(domid)
}
fn console_device_add(
&mut self,
dom_path: &String,
domid: u32,
port: u32,
mfn: u64,
) -> Result<(), XenClientError> {
let frontend_path = format!("{}/console", dom_path);
let backend_path = format!("{}/backend/console/{}/{}", dom_path, domid, 0);
let mut tx = self.store.transaction()?;
tx.write_string(
format!("{}/frontend-id", backend_path).as_str(),
&domid.to_string(),
)?;
tx.write_string(
format!("{}/online", backend_path).as_str(),
"1",
)?;
tx.write_string(format!("{}/state", backend_path).as_str(), "1")?;
tx.write_string(
format!("{}/protocol", backend_path).as_str(),
"vt100",
)?;
for (key, value) in config.clone_domain_entries() {
let path = format!("{}/{}", domain, key);
tx.write(path.as_str(), value.into_bytes())?;
}
let domid_path = format!("{}/domid", domain);
tx.write(domid_path.as_str(), domid.to_string().into_bytes())?;
for (key, value) in config.clone_domain_entries() {
let path = format!("{}/{}", vm, key);
tx.write(path.as_str(), value.into_bytes())?;
}
tx.write_string(
format!("{}/backend-id", frontend_path).as_str(),
"0",
)?;
tx.write_string(
format!("{}/limit", frontend_path).as_str(),
"1048576",
)?;
tx.write_string(
format!("{}/type", frontend_path).as_str(),
"xenconsoled",
)?;
tx.write_string(
format!("{}/output", frontend_path).as_str(),
"pty",
)?;
tx.write_string(format!("{}/tty", frontend_path).as_str(), "")?;
tx.write_string(
format!("{}/port", frontend_path).as_str(),
&port.to_string(),
)?;
tx.write_string(
format!("{}/ring-ref", frontend_path).as_str(),
&mfn.to_string(),
)?;
tx.commit()?;
Ok(())
}
}