diff --git a/xenclient/examples/boot.rs b/xenclient/examples/boot.rs index a73156a..b9a34a2 100644 --- a/xenclient/examples/boot.rs +++ b/xenclient/examples/boot.rs @@ -23,7 +23,8 @@ fn main() -> Result<(), XenClientError> { let image_loader = ElfImageLoader::load_file_kernel(kernel_image_path.as_str())?; let memctl = MemoryControl::new(&call); let mut boot = BootSetup::new(&call, &domctl, &memctl, domid); - boot.initialize(&image_loader, 512 * 1024)?; + let mut state = boot.initialize(&image_loader, 512 * 1024)?; + boot.boot(&mut state, "debug")?; domctl.destroy_domain(domid)?; println!("domain destroyed: {}", domid); Ok(()) diff --git a/xenclient/src/boot.rs b/xenclient/src/boot.rs index 97b244f..923ddec 100644 --- a/xenclient/src/boot.rs +++ b/xenclient/src/boot.rs @@ -2,9 +2,15 @@ use crate::mem::PhysicalPages; use crate::sys::{ SUPERPAGE_2MB_NR_PFNS, SUPERPAGE_2MB_SHIFT, SUPERPAGE_BATCH_SIZE, XEN_PAGE_SHIFT, }; +use crate::x86::{ + PageTable, PageTableMapping, StartInfo, MAX_GUEST_CMDLINE, X86_GUEST_MAGIC, X86_PAGE_SHIFT, + X86_PAGE_SIZE, X86_PAGE_TABLE_MAX_MAPPINGS, X86_PGTABLE_LEVELS, X86_PGTABLE_LEVEL_SHIFT, + X86_VIRT_MASK, +}; use crate::XenClientError; -use libc::memset; +use libc::{c_char, memset}; use log::debug; +use std::cmp::{max, min}; use std::ffi::c_void; use std::slice; use xencall::domctl::DomainControl; @@ -13,13 +19,14 @@ use xencall::XenCall; pub trait BootImageLoader { fn parse(&self) -> Result; - fn load(&self, image_info: BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError>; + fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError>; } pub const XEN_UNSET_ADDR: u64 = -1i64 as u64; #[derive(Debug)] pub struct BootImageInfo { + pub virt_base: u64, pub virt_kstart: u64, pub virt_kend: u64, pub virt_hypercall: u64, @@ -34,11 +41,13 @@ pub struct BootSetup<'a> { domid: u32, virt_alloc_end: u64, pfn_alloc_end: u64, + virt_pgtab_end: u64, + total_pages: u64, } #[derive(Debug)] -struct DomainSegment { - _vstart: u64, +pub struct DomainSegment { + vstart: u64, _vend: u64, pfn: u64, addr: u64, @@ -53,6 +62,16 @@ struct VmemRange { _nid: u32, } +pub struct BootState { + pub kernel_segment: DomainSegment, + pub start_info_segment: DomainSegment, + pub xenstore_segment: DomainSegment, + pub console_segment: DomainSegment, + pub boot_stack_segment: DomainSegment, + pub page_table_segment: DomainSegment, + pub page_table: PageTable, +} + impl BootSetup<'_> { pub fn new<'a>( call: &'a XenCall, @@ -67,6 +86,8 @@ impl BootSetup<'_> { domid, virt_alloc_end: 0, pfn_alloc_end: 0, + virt_pgtab_end: 0, + total_pages: 0, } } @@ -95,6 +116,8 @@ impl BootSetup<'_> { )); } + self.total_pages = total; + let mut p2m = vec![-1i64 as u64; p2m_size as usize]; for range in &vmemranges { let mut extents = vec![0u64; SUPERPAGE_BATCH_SIZE as usize]; @@ -184,30 +207,235 @@ impl BootSetup<'_> { &mut self, image_loader: &dyn BootImageLoader, memkb: u64, - ) -> Result<(), XenClientError> { + ) -> Result { debug!("BootSetup initialize memkb={:?}", memkb); - let image_info = image_loader.parse()?; - debug!("BootSetup initialize image_info={:?}", image_info); self.domctl.set_max_mem(self.domid, memkb)?; self.initialize_memory(memkb)?; + + let image_info = image_loader.parse()?; + let kernel_segment = self.load_kernel_segment(image_loader, &image_info)?; + let start_info_segment = self.alloc_page()?; + let xenstore_segment = self.alloc_page()?; + let console_segment = self.alloc_page()?; + let boot_stack_segment = self.alloc_page()?; + let (page_table_segment, page_table) = self.alloc_page_tables(&image_info)?; + Ok(BootState { + kernel_segment, + start_info_segment, + xenstore_segment, + console_segment, + boot_stack_segment, + page_table_segment, + page_table, + }) + } + + pub fn boot(&mut self, state: &mut BootState, cmdline: &str) -> Result<(), XenClientError> { + self.setup_page_tables(state)?; + self.setup_start_info(state, cmdline); + Ok(()) + } + + fn setup_page_tables(&mut self, state: &mut BootState) -> Result<(), XenClientError> { + for lvl_idx in (0usize..3usize).rev() { + for map_idx_1 in 0usize..state.page_table.mappings_count { + let map1 = &state.page_table.mappings[map_idx_1]; + let from = map1.levels[lvl_idx].from; + let to = map1.levels[lvl_idx].to; + let pg = self.phys.pfn_to_ptr(map1.levels[lvl_idx].pfn, 0)? as *mut u64; + for map_idx_2 in 0usize..state.page_table.mappings_count { + let map2 = &state.page_table.mappings[map_idx_2]; + let lvl = if lvl_idx > 0 { + &map2.levels[lvl_idx - 1] + } else { + &map2.area + }; + + if lvl_idx > 0 && lvl.pgtables == 0 { + continue; + } + + if lvl.from >= to || lvl.to <= from { + continue; + } + + let p_s = (max(from, lvl.from) - from) + >> (X86_PAGE_SHIFT + lvl_idx as u64 * X86_PGTABLE_LEVEL_SHIFT); + let p_e = (min(to, lvl.to) - from) + >> (X86_PAGE_SHIFT + lvl_idx as u64 * X86_PGTABLE_LEVEL_SHIFT); + let mut pfn = (max(from, lvl.from) - from) >> ((X86_PAGE_SHIFT + lvl_idx as u64 * X86_PGTABLE_LEVEL_SHIFT) + lvl.pfn); + + for p in p_s..p_e + 1 { + unsafe { + *pg.add(p as usize) = self.phys.p2m[pfn as usize] << X86_PAGE_SHIFT; + } + pfn += 1; + } + } + } + } + Ok(()) + } + + fn setup_start_info(&mut self, state: &BootState, cmdline: &str) { + let info = state.start_info_segment.addr as *mut StartInfo; + unsafe { + for (i, c) in X86_GUEST_MAGIC.chars().enumerate() { + (*info).magic[i] = c as c_char; + } + (*info).nr_pages = self.total_pages; + (*info).shared_info = 0; + (*info).pt_base = state.page_table_segment.vstart; + (*info).nr_pt_frames = state.page_table.mappings[0].area.pgtables as u64; + (*info).mfn_list = 0; + (*info).first_p2m_pfn = 0; + (*info).nr_p2m_frames = 0; + (*info).flags = 0; + (*info).store_evtchn = 0; + (*info).store_mfn = 0; + (*info).console.mfn = self.phys.p2m[state.console_segment.pfn as usize]; + (*info).console.evtchn = 0; + (*info).mod_start = 0; + (*info).mod_len = 0; + for (i, c) in cmdline.chars().enumerate() { + (*info).cmdline[i] = c as c_char; + (*info).cmdline[MAX_GUEST_CMDLINE - 1] = 0; + } + debug!("BootSetup setup_start_info={:?}", *info); + } + } + + fn load_kernel_segment( + &mut self, + image_loader: &dyn BootImageLoader, + image_info: &BootImageInfo, + ) -> Result { let kernel_segment = self.alloc_segment(image_info.virt_kend - image_info.virt_kstart)?; let kernel_segment_ptr = kernel_segment.addr as *mut u8; debug!( "BootSetup initialize kernel_segment ptr={:#x}", kernel_segment_ptr as u64 ); - let slice = + let kernel_segment_slice = unsafe { slice::from_raw_parts_mut(kernel_segment_ptr, kernel_segment.size as usize) }; - image_loader.load(image_info, slice)?; + image_loader.load(image_info, kernel_segment_slice)?; + Ok(kernel_segment) + } + + fn count_page_tables( + &mut self, + table: &mut PageTable, + from: u64, + to: u64, + pfn: u64, + ) -> Result<(), XenClientError> { + if table.mappings_count == X86_PAGE_TABLE_MAX_MAPPINGS { + return Err(XenClientError::new("too many mappings")); + } + + let pfn_end = pfn + ((to - from) >> X86_PAGE_SHIFT); + if pfn_end >= self.phys.p2m_size() { + return Err(XenClientError::new("not enough memory for initial mapping")); + } + + for mapping in &table.mappings { + if from < mapping.area.to && to > mapping.area.from { + return Err(XenClientError::new("overlapping mappings")); + } + } + + table.mappings[table.mappings_count] = PageTableMapping::default(); + let compare_table = table.clone(); + let map = &mut table.mappings[table.mappings_count]; + map.area.from = from & X86_VIRT_MASK; + map.area.to = to & X86_VIRT_MASK; + + for lvl_index in (0usize..3usize).rev() { + let lvl = &mut map.levels[lvl_index]; + lvl.pfn = self.pfn_alloc_end + map.area.pgtables as u64; + if lvl_index as u64 == X86_PGTABLE_LEVELS - 1 { + if table.mappings_count == 0 { + lvl.from = 0; + lvl.to = X86_VIRT_MASK; + lvl.pgtables = 1; + map.area.pgtables += 1; + } + continue; + } + + let bits = X86_PAGE_SHIFT + (lvl_index + 1) as u64 * X86_PGTABLE_LEVEL_SHIFT; + let mask = (1 << bits) - 1; + lvl.from = map.area.from & !mask; + lvl.to = map.area.to | mask; + + for cmp in &compare_table.mappings { + let cmp_lvl = &cmp.levels[lvl_index]; + if cmp_lvl.from == cmp_lvl.to { + continue; + } + + if lvl.from >= cmp_lvl.from && lvl.to <= cmp_lvl.to { + lvl.from = 0; + lvl.to = 0; + } + + if lvl.from >= cmp_lvl.from && lvl.from <= cmp_lvl.to { + lvl.from = cmp_lvl.to + 1; + } + + if lvl.to >= cmp_lvl.from && lvl.to <= cmp_lvl.to { + lvl.to = cmp_lvl.from - 1; + } + } + + if lvl.from < lvl.to { + lvl.pgtables = (((lvl.to - lvl.from) >> bits) + 1) as usize; + } + + map.area.pgtables += lvl.pgtables; + } Ok(()) } + fn alloc_page_tables( + &mut self, + image_info: &BootImageInfo, + ) -> Result<(DomainSegment, PageTable), XenClientError> { + let mut table = PageTable::default(); + let extra_pages = ((512 * 1024) / X86_PAGE_SIZE) + 1; + let mut pages = extra_pages; + + let mut try_virt_end: u64; + loop { + try_virt_end = (self.virt_alloc_end + pages * X86_PAGE_SIZE) | (1 << 22); + self.count_page_tables(&mut table, image_info.virt_base, try_virt_end, 0)?; + pages = table.mappings[0].area.pgtables as u64 + extra_pages; + if self.virt_alloc_end + pages * X86_PAGE_SIZE <= try_virt_end + 1 { + break; + } + } + + let segment: DomainSegment; + { + let map = &mut table.mappings[table.mappings_count]; + map.area.pfn = 0; + table.mappings_count += 1; + self.virt_pgtab_end = try_virt_end + 1; + segment = self.alloc_segment(map.area.pgtables as u64 * X86_PAGE_SIZE)?; + } + debug!( + "BootSetup alloc_page_tables table={:?} segment={:?}", + table, segment + ); + Ok((segment, table)) + } + fn alloc_segment(&mut self, size: u64) -> Result { let page_size = 1u64 << XEN_PAGE_SHIFT; let pages = (size + page_size - 1) / page_size; let start = self.virt_alloc_end; let mut segment = DomainSegment { - _vstart: start, + vstart: start, _vend: 0, pfn: self.pfn_alloc_end, addr: 0, @@ -225,4 +453,9 @@ impl BootSetup<'_> { debug!("BootSetup alloc_segment size={} ptr={:#x}", size, ptr); Ok(segment) } + + fn alloc_page(&mut self) -> Result { + let page_size = 1u64 << XEN_PAGE_SHIFT; + self.alloc_segment(page_size) + } } diff --git a/xenclient/src/elfloader.rs b/xenclient/src/elfloader.rs index 4837be3..bd35089 100644 --- a/xenclient/src/elfloader.rs +++ b/xenclient/src/elfloader.rs @@ -265,11 +265,7 @@ impl BootImageLoader for ElfImageLoader { )); } - let _virt_base = if virt_base == XEN_UNSET_ADDR { - 0 - } else { - virt_base - }; + let virt_base = 0; let _paddr_offset = if paddr_offset == XEN_UNSET_ADDR { 0 @@ -287,6 +283,7 @@ impl BootImageLoader for ElfImageLoader { }; Ok(BootImageInfo { + virt_base, virt_kstart, virt_kend, virt_hypercall, @@ -295,7 +292,7 @@ impl BootImageLoader for ElfImageLoader { }) } - fn load(&self, image_info: BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError> { + fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError> { let elf = ElfBytes::::minimal_parse(self.data.as_slice())?; let segments = elf.segments().ok_or(XenClientError::new( "Unable to parse kernel image: segments not found.", diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index b7345ff..8aa261e 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -3,6 +3,7 @@ pub mod create; pub mod elfloader; pub mod mem; pub mod sys; +mod x86; use crate::create::DomainConfig; use std::error::Error; diff --git a/xenclient/src/mem.rs b/xenclient/src/mem.rs index f5ffa8c..635aa0c 100644 --- a/xenclient/src/mem.rs +++ b/xenclient/src/mem.rs @@ -12,7 +12,7 @@ pub struct PhysicalPage { pub struct PhysicalPages<'a> { domid: u32, - p2m: Vec, + pub(crate) p2m: Vec, call: &'a XenCall, pages: Vec, } @@ -31,6 +31,10 @@ impl PhysicalPages<'_> { self.p2m = p2m; } + pub fn p2m_size(&mut self) -> u64 { + self.p2m.len() as u64 + } + pub fn pfn_to_ptr(&mut self, pfn: u64, count: u64) -> Result { for page in &self.pages { if pfn >= page.pfn + page.count { diff --git a/xenclient/src/x86.rs b/xenclient/src/x86.rs new file mode 100644 index 0000000..619d893 --- /dev/null +++ b/xenclient/src/x86.rs @@ -0,0 +1,73 @@ +use libc::c_char; + +pub const X86_PAGE_SHIFT: u64 = 12; +pub const X86_PAGE_SIZE: u64 = 1 << X86_PAGE_SHIFT; +pub const X86_VIRT_BITS: u64 = 48; +pub const X86_VIRT_MASK: u64 = 1 << X86_VIRT_BITS; +pub const X86_PGTABLE_LEVELS: u64 = 4; +pub const X86_PGTABLE_LEVEL_SHIFT: u64 = 9; + +#[repr(C)] +#[derive(Debug, Clone)] +#[derive(Default)] +pub struct PageTableMappingLevel { + pub from: u64, + pub to: u64, + pub pfn: u64, + pub pgtables: usize, +} + +#[repr(C)] +#[derive(Debug, Clone)] +#[derive(Default)] +pub struct PageTableMapping { + pub area: PageTableMappingLevel, + pub levels: [PageTableMappingLevel; X86_PGTABLE_LEVELS as usize], +} + +pub const X86_PAGE_TABLE_MAX_MAPPINGS: usize = 2; + +#[repr(C)] +#[derive(Debug, Clone)] +#[derive(Default)] +pub struct PageTable { + pub mappings_count: usize, + pub mappings: [PageTableMapping; X86_PAGE_TABLE_MAX_MAPPINGS], +} + + + + + + + +#[repr(C)] +#[derive(Debug)] +pub struct StartInfoConsole { + pub mfn: u64, + pub evtchn: u32, +} + +pub const MAX_GUEST_CMDLINE: usize = 1024; + +#[repr(C)] +#[derive(Debug)] +pub struct StartInfo { + pub magic: [c_char; 32], + pub nr_pages: u64, + pub shared_info: u64, + pub flags: u32, + pub store_mfn: u64, + pub store_evtchn: u32, + pub console: StartInfoConsole, + pub pt_base: u64, + pub nr_pt_frames: u64, + pub mfn_list: u64, + pub mod_start: u64, + pub mod_len: u64, + pub cmdline: [c_char; MAX_GUEST_CMDLINE], + pub first_p2m_pfn: u64, + pub nr_p2m_frames: u64, +} + +pub const X86_GUEST_MAGIC: &str = "xen-3.0-x86_64";