1mod arch;
4mod busy_wait;
5mod convert;
6mod core;
7
8use std::{
9 cell::{Cell, RefCell},
10 collections::HashMap,
11 os::fd::AsRawFd as _,
12 time::{Duration, Instant},
13};
14
15use vmi_core::{
16 Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiDriver, VmiError,
17 VmiEvent, VmiEventResponse, VmiInfo, VmiMappedPage,
18 driver::{
19 VmiEventControl, VmiQueryProtection, VmiQueryRegisters, VmiRead, VmiSetProtection,
20 VmiSetRegisters, VmiViewControl, VmiVmControl, VmiWrite,
21 },
22};
23pub use xen::XenDomainId; use xen::{
25 XenAltP2M, XenAltP2MView, XenControl, XenDeviceModel, XenDomain, XenDomainInfo,
26 XenEventChannelPort, XenForeignMemory, XenForeignMemoryProtection, XenMonitor, XenStore,
27 ctrl::VmEventRing,
28};
29
30use self::convert::{FromExt, IntoExt, TryFromExt};
31pub use self::{arch::ArchAdapter, busy_wait::VmiXenBusyWait};
32
33struct XenView {
34 view: XenAltP2MView,
35 default_access: xen::MemoryAccess,
36}
37
38fn is_missing_altp2m_entry(err: &xen::XenError) -> bool {
39 matches!(
40 err,
41 xen::XenError::Io(inner) if inner.raw_os_error() == Some(libc::ESRCH)
42 )
43}
44
45pub struct VmiXenDriver<Arch>
47where
48 Arch: ArchAdapter,
49{
50 pub(crate) domain: XenDomain<Arch::XenArch>,
51 pub(crate) devicemodel: XenDeviceModel,
52 pub(crate) monitor: XenMonitor,
53 pub(crate) altp2m: XenAltP2M,
54 pub(crate) evtchn: XenEventChannelPort,
55 pub(crate) foreign_memory: XenForeignMemory,
56 pub(crate) info: XenDomainInfo,
57
58 pub(crate) ring: RefCell<VmEventRing>,
59 pub(crate) views: RefCell<HashMap<u16, XenView>>,
60 pub(crate) event_processing_overhead: Cell<Duration>,
61
62 pub(crate) busy_wait: VmiXenBusyWait,
63 pub(crate) busy_window: Cell<Duration>,
64 pub(crate) masked: Cell<bool>,
65 pub(crate) answered: Cell<Option<Instant>>,
66}
67
68impl<Arch> Drop for VmiXenDriver<Arch>
69where
70 Arch: ArchAdapter,
71{
72 fn drop(&mut self) {
73 let max_memkb = self.info.max_pages * Arch::PAGE_SIZE / 1024;
74
75 let _ = self.domain.set_max_mem(max_memkb);
76 let _ = self.monitor.emul_unimplemented(false);
77 let _ = self.monitor.inguest_pagefault(false);
78 }
79}
80
81impl<Arch> VmiXenDriver<Arch>
82where
83 Arch: ArchAdapter,
84{
85 pub fn new(domain_id: XenDomainId) -> Result<Self, VmiError> {
87 let xc = XenControl::new().map_err(VmiError::driver)?;
88 let domain = xc.domain(domain_id).map_err(VmiError::driver)?;
89 domain.set_max_mem(u64::MAX).map_err(VmiError::driver)?;
90
91 let devicemodel = domain.device_model().map_err(VmiError::driver)?;
92 let (monitor, ring) = domain.monitor().map_err(VmiError::driver)?;
93 let altp2m = domain.altp2m().map_err(VmiError::driver)?;
94 let evtchn = monitor.channel().map_err(VmiError::driver)?;
95 let foreign_memory = XenForeignMemory::new().map_err(VmiError::driver)?;
96 let info = domain.info().map_err(VmiError::driver)?;
97
98 monitor.inguest_pagefault(true).map_err(VmiError::driver)?;
99 monitor.emul_unimplemented(true).map_err(VmiError::driver)?;
100
101 Ok(Self {
102 domain,
103 devicemodel,
104 monitor,
105 altp2m,
106 evtchn,
107 foreign_memory,
108 info,
109 ring: RefCell::new(ring),
110 views: RefCell::new(HashMap::new()),
111 event_processing_overhead: Cell::new(Duration::ZERO),
112 busy_wait: VmiXenBusyWait::default(),
113 busy_window: Cell::new(Duration::ZERO),
114 masked: Cell::new(false),
115 answered: Cell::new(None),
116 })
117 }
118
119 pub fn try_from_env() -> Result<Option<Self>, VmiError> {
132 fn resolve_domain(domain_name: &str) -> Result<Option<XenDomainId>, VmiError> {
133 XenStore::new()
134 .map_err(VmiError::driver)?
135 .domain_id_from_name(domain_name)
136 .map_err(VmiError::driver)
137 }
138
139 if let Ok(var) = std::env::var("VMI_XEN_DOMAIN") {
140 if let Ok(domain_id) = var.parse() {
141 return Ok(Some(Self::new(XenDomainId(domain_id))?));
142 }
143
144 if let Some(domain_id) = resolve_domain(&var)? {
145 return Ok(Some(Self::new(domain_id)?));
146 }
147 }
148 else if let Ok(var) = std::env::var("VMI_XEN_DOMAIN_ID") {
149 match var.parse() {
150 Ok(domain_id) => return Ok(Some(Self::new(XenDomainId(domain_id))?)),
151 Err(err) => tracing::error!(%err, var, "invalid value for VMI_XEN_DOMAIN_ID"),
152 }
153 }
154 else if let Ok(var) = std::env::var("VMI_XEN_DOMAIN_NAME")
155 && let Some(domain_id) = resolve_domain(&var)?
156 {
157 return Ok(Some(Self::new(domain_id)?));
158 }
159
160 Ok(None)
161 }
162
163 pub fn with_busy_wait(mut self, busy_wait: VmiXenBusyWait) -> Self {
168 self.busy_wait = busy_wait;
169 self
170 }
171
172 pub fn event_channel_port(&self) -> u32 {
178 self.evtchn.local_port()
179 }
180
181 fn busy_wait_for_request(&self) -> Result<bool, VmiError> {
186 if !self.masked.get() {
187 return Ok(false);
188 }
189
190 let end = Instant::now() + self.busy_window.get();
191 loop {
192 if self.ring.borrow().has_unconsumed_requests() {
193 return Ok(true);
194 }
195
196 if Instant::now() >= end {
197 break;
198 }
199
200 std::hint::spin_loop();
201 }
202
203 self.evtchn.unmask().map_err(VmiError::driver)?;
204 self.masked.set(false);
205 Ok(false)
206 }
207
208 fn sleep_for_request(&self, deadline: Instant) -> Result<(), VmiError> {
214 let mut fds = [libc::pollfd {
215 fd: self.evtchn.as_raw_fd(),
216 events: libc::POLLIN | libc::POLLERR,
217 revents: 0,
218 }];
219
220 loop {
221 let remaining = deadline.saturating_duration_since(Instant::now());
222 let timeout = remaining
223 .as_micros()
224 .div_ceil(1000)
225 .try_into()
226 .map_err(|_| VmiError::InvalidTimeout)?;
227
228 #[rustfmt::skip]
229 let poll_result = unsafe {
230 libc::poll(
231 fds.as_mut_ptr() as _,
232 fds.len() as _,
233 timeout
234 )
235 };
236
237 match poll_result {
238 0 => return Err(VmiError::Timeout),
239 -1 => return Err(VmiError::Io(std::io::Error::last_os_error())),
240 _ => (),
241 }
242
243 let port = self.evtchn.pending().map_err(VmiError::driver)?;
244 assert_eq!(port, self.evtchn.local_port());
245
246 if self.ring.borrow().has_unconsumed_requests() {
247 break;
248 }
249
250 self.evtchn.unmask().map_err(VmiError::driver)?;
253 }
254
255 if let Some(answered) = self.answered.get() {
256 let window = self
257 .busy_wait
258 .next_window(self.busy_window.get(), answered.elapsed());
259 self.busy_window.set(window);
260 }
261
262 if self.busy_window.get().is_zero() {
263 self.evtchn.unmask().map_err(VmiError::driver)?;
264 }
265 else {
266 self.masked.set(true);
267 }
268
269 Ok(())
270 }
271}
272
273impl<Arch> VmiDriver for VmiXenDriver<Arch>
274where
275 Arch: ArchAdapter,
276{
277 type Architecture = Arch;
278
279 fn info(&self) -> Result<VmiInfo, VmiError> {
280 Ok(VmiInfo {
281 page_size: Arch::PAGE_SIZE,
282 page_shift: Arch::PAGE_SHIFT,
283 max_gfn: Gfn::new(self.domain.maximum_gpfn().map_err(VmiError::driver)?),
284 vcpus: self.info.max_vcpu_id + 1,
285 })
286 }
287}
288
289impl<Arch> VmiRead for VmiXenDriver<Arch>
290where
291 Arch: ArchAdapter,
292{
293 fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
294 let page = self
295 .foreign_memory
296 .map(
297 self.domain.id(),
298 XenForeignMemoryProtection::READ,
299 &[u64::from(gfn)],
300 None,
301 )
302 .map_err(VmiError::driver)?;
303
304 Ok(VmiMappedPage::new(page))
305 }
306}
307
308impl<Arch> VmiWrite for VmiXenDriver<Arch>
309where
310 Arch: ArchAdapter,
311{
312 fn write_page(&self, gfn: Gfn, offset: u64, content: &[u8]) -> Result<VmiMappedPage, VmiError> {
313 let mut page = self
314 .foreign_memory
315 .map(
316 self.domain.id(),
317 XenForeignMemoryProtection::WRITE,
318 &[u64::from(gfn)],
319 None,
320 )
321 .map_err(VmiError::driver)?;
322
323 let offset = offset as usize;
324 if offset + content.len() > Arch::PAGE_SIZE as usize {
325 return Err(VmiError::OutOfBounds);
326 }
327
328 page[offset..offset + content.len()].copy_from_slice(content);
329
330 Ok(VmiMappedPage::new(page))
331 }
332}
333
334impl<Arch> VmiQueryProtection for VmiXenDriver<Arch>
335where
336 Arch: ArchAdapter,
337{
338 fn memory_access(&self, gfn: Gfn, view: View) -> Result<MemoryAccess, VmiError> {
339 Ok(self.memory_access_with_options(gfn, view)?.0)
340 }
341
342 fn memory_access_with_options(
343 &self,
344 gfn: Gfn,
345 view: View,
346 ) -> Result<(MemoryAccess, MemoryAccessOptions), VmiError> {
347 if view.0 == 0 {
356 let xen_access = self
357 .domain
358 .get_mem_access(gfn.0)
359 .map_err(VmiError::driver)?;
360
361 let access = MemoryAccess::from_ext(xen_access);
362 let options = MemoryAccessOptions::from_ext(xen_access);
363
364 tracing::trace!(%gfn, %view, ?xen_access, "get memory access");
365
366 return Ok((access, options));
367 }
368
369 let views = self.views.borrow();
370 let xen_view = match views.get(&view.0) {
371 Some(view) => view,
372 None => return Err(VmiError::ViewNotFound),
373 };
374
375 let xen_access = match xen_view.view.get_mem_access(gfn.0) {
376 Ok(xen_access) => xen_access,
377 Err(err) if is_missing_altp2m_entry(&err) => {
378 self.domain
381 .get_mem_access(gfn.0)
382 .map_err(VmiError::driver)?;
383
384 let xen_access = xen_view.default_access;
385
386 let access = MemoryAccess::from_ext(xen_access);
387 let options = MemoryAccessOptions::from_ext(xen_access);
388
389 tracing::trace!(%gfn, %view, ?xen_access, "get memory access (default)");
390
391 return Ok((access, options));
392 }
393 Err(err) => return Err(VmiError::driver(err)),
394 };
395
396 let access = MemoryAccess::from_ext(xen_access);
397 let options = MemoryAccessOptions::from_ext(xen_access);
398
399 tracing::trace!(%gfn, %view, ?xen_access, "get memory access");
400
401 Ok((access, options))
402 }
403}
404
405impl<Arch> VmiSetProtection for VmiXenDriver<Arch>
406where
407 Arch: ArchAdapter,
408{
409 fn set_memory_access(
410 &self,
411 gfn: Gfn,
412 view: View,
413 access: MemoryAccess,
414 ) -> Result<(), VmiError> {
415 tracing::trace!(%gfn, %view, %access, "set memory access");
416
417 if view.0 == 0 {
418 return self
419 .domain
420 .set_mem_access(gfn.into(), access.into_ext())
421 .map_err(VmiError::driver);
422 }
423
424 match self.views.borrow().get(&view.0) {
425 Some(view) => view
426 .view
427 .set_mem_access(gfn.into(), access.into_ext())
428 .map_err(VmiError::driver),
429 None => Err(VmiError::ViewNotFound),
430 }
431 }
432
433 fn set_memory_access_with_options(
434 &self,
435 gfn: Gfn,
436 view: View,
437 access: MemoryAccess,
438 options: MemoryAccessOptions,
439 ) -> Result<(), VmiError> {
440 tracing::trace!(%gfn, %view, %access, "set memory access");
441
442 let mut xen_access = access.into_ext();
443
444 if options.contains(MemoryAccessOptions::IGNORE_PAGE_WALK_UPDATES) {
445 if access != MemoryAccess::R {
446 return Err(VmiError::NotSupported);
447 }
448
449 xen_access = xen::MemoryAccess::R_PW;
450 }
451
452 if view.0 == 0 {
453 return self
454 .domain
455 .set_mem_access(gfn.into(), xen_access)
456 .map_err(VmiError::driver);
457 }
458
459 match self.views.borrow().get(&view.0) {
460 Some(view) => view
461 .view
462 .set_mem_access(gfn.into(), xen_access)
463 .map_err(VmiError::driver),
464 None => Err(VmiError::ViewNotFound),
465 }
466 }
467}
468
469impl<Arch> VmiQueryRegisters for VmiXenDriver<Arch>
470where
471 Arch: ArchAdapter,
472{
473 fn registers(&self, vcpu: VcpuId) -> Result<Arch::Registers, VmiError> {
474 Arch::registers(self, vcpu)
475 }
476}
477
478impl<Arch> VmiSetRegisters for VmiXenDriver<Arch>
479where
480 Arch: ArchAdapter,
481{
482 fn set_registers(&self, vcpu: VcpuId, registers: Arch::Registers) -> Result<(), VmiError> {
483 Arch::set_registers(self, vcpu, registers)
484 }
485}
486
487impl<Arch> VmiViewControl for VmiXenDriver<Arch>
488where
489 Arch: ArchAdapter,
490{
491 fn default_view(&self) -> View {
492 View(0)
493 }
494
495 fn create_view(&self, default_access: MemoryAccess) -> Result<View, VmiError> {
496 let default_access = default_access.into_ext();
497
498 let view = self
499 .altp2m
500 .create_view(default_access)
501 .map_err(VmiError::driver)?;
502
503 let id = view.id();
504 self.views.borrow_mut().insert(
505 id,
506 XenView {
507 view,
508 default_access,
509 },
510 );
511
512 Ok(View(id))
513 }
514
515 fn destroy_view(&self, view: View) -> Result<(), VmiError> {
516 if view.0 == 0 {
517 return Ok(());
518 }
519
520 match self.views.borrow_mut().remove(&view.0) {
521 Some(_view) => Ok(()),
523 None => Err(VmiError::ViewNotFound),
524 }
525 }
526
527 fn switch_to_view(&self, view: View) -> Result<(), VmiError> {
528 if view.0 == 0 {
529 return self.altp2m.reset_view().map_err(VmiError::driver);
530 }
531
532 match self.views.borrow().get(&view.0) {
533 Some(view) => view.view.switch().map_err(VmiError::driver),
534 None => Err(VmiError::ViewNotFound),
535 }
536 }
537
538 fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> {
539 if view.0 == 0 {
540 return Ok(());
541 }
542
543 match self.views.borrow().get(&view.0) {
544 Some(view) => view
546 .view
547 .change_gfn(old_gfn.into(), new_gfn.into())
548 .map_err(VmiError::driver),
549 None => Err(VmiError::ViewNotFound),
550 }
551 }
552
553 fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> {
554 if view.0 == 0 {
555 return Ok(());
556 }
557
558 match self.views.borrow().get(&view.0) {
559 Some(view) => view
561 .view
562 .change_gfn(gfn.into(), u64::MAX)
563 .map_err(VmiError::driver),
564 None => Err(VmiError::ViewNotFound),
565 }
566 }
567}
568
569impl<Arch> VmiEventControl for VmiXenDriver<Arch>
570where
571 Arch: ArchAdapter,
572{
573 fn monitor_enable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> {
574 Arch::monitor_enable(self, option)
575 }
576
577 fn monitor_disable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> {
578 Arch::monitor_disable(self, option)
579 }
580
581 fn events_pending(&self) -> usize {
582 self.ring.borrow().unconsumed_requests()
583 }
584
585 fn event_processing_overhead(&self) -> Duration {
586 self.event_processing_overhead.get()
587 }
588
589 fn wait_for_event(
590 &self,
591 timeout: Duration,
592 mut handler: impl FnMut(&VmiEvent<Arch>) -> VmiEventResponse<Arch>,
593 ) -> Result<(), VmiError> {
594 i32::try_from(timeout.as_millis()).map_err(|_| VmiError::InvalidTimeout)?;
595 let deadline = Instant::now() + timeout;
596
597 if !self.busy_wait_for_request()? {
598 self.sleep_for_request(deadline)?;
599 }
600
601 struct OverheadGuard<'a, Arch>
602 where
603 Arch: ArchAdapter,
604 {
605 driver: &'a VmiXenDriver<Arch>,
606 start: Instant,
607 }
608
609 impl<'a, Arch> OverheadGuard<'a, Arch>
610 where
611 Arch: ArchAdapter,
612 {
613 fn new(driver: &'a VmiXenDriver<Arch>) -> Self {
614 Self {
615 driver,
616 start: Instant::now(),
617 }
618 }
619 }
620
621 impl<Arch> Drop for OverheadGuard<'_, Arch>
622 where
623 Arch: ArchAdapter,
624 {
625 fn drop(&mut self) {
626 let elapsed = Instant::now().duration_since(self.start);
627 let overhead = &self.driver.event_processing_overhead;
628 overhead.set(overhead.get() + elapsed);
629 }
630 }
631
632 {
633 let _overhead_guard = OverheadGuard::new(self);
634
635 while self.ring.borrow().has_unconsumed_requests() {
636 let mut event = self.ring.borrow_mut().get_request();
637 Arch::process_event(self, &mut event, &mut handler)?;
638 self.ring.borrow_mut().put_response(event);
639 }
640 }
641
642 self.evtchn.notify().map_err(VmiError::driver)?;
643 self.answered.set(Some(Instant::now()));
644
645 Ok(())
646 }
647}
648
649impl<Arch> VmiVmControl for VmiXenDriver<Arch>
650where
651 Arch: ArchAdapter,
652{
653 fn pause(&self) -> Result<(), VmiError> {
654 self.domain.pause().map_err(VmiError::driver)
655 }
656
657 fn resume(&self) -> Result<(), VmiError> {
658 self.domain.unpause().map_err(VmiError::driver)
659 }
660
661 fn allocate_gfn(&self) -> Result<Gfn, VmiError> {
662 let gfn = Gfn::new(self.domain.maximum_gpfn().map_err(VmiError::driver)?) + 1;
663 self.allocate_gfn_at(gfn)?;
664 Ok(gfn)
665 }
666
667 fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError> {
668 self.domain
669 .populate_physmap_exact(0, 0, &[gfn.into()])
670 .map_err(VmiError::driver)
671 }
672
673 fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> {
674 self.domain
675 .decrease_reservation_exact(0, &[gfn.into()])
676 .map_err(VmiError::driver)
677 }
678
679 fn inject_interrupt(&self, vcpu: VcpuId, interrupt: Arch::Interrupt) -> Result<(), VmiError> {
680 Arch::inject_interrupt(self, vcpu, interrupt)
681 }
682
683 fn reset_state(&self) -> Result<(), VmiError> {
684 Arch::reset_state(self)
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::is_missing_altp2m_entry;
691
692 #[test]
693 fn identifies_missing_altp2m_entries() {
694 let missing = xen::XenError::Io(std::io::Error::from_raw_os_error(libc::ESRCH));
695 let invalid = xen::XenError::Io(std::io::Error::from_raw_os_error(libc::EINVAL));
696
697 assert!(is_missing_altp2m_entry(&missing));
698 assert!(!is_missing_altp2m_entry(&invalid));
699 assert!(!is_missing_altp2m_entry(&xen::XenError::Other(
700 "other error"
701 )));
702 }
703}