Skip to main content

vmi_driver_xen/
lib.rs

1//! VMI driver for Xen hypervisor.
2
3mod 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; // Only `libxen` type in `VmiXenDriver`'s public API.
24use 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
45/// VMI driver for Xen hypervisor.
46pub 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    /// Creates a new VMI driver for Xen hypervisor.
86    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    /// Creates a new VMI driver from environment variables.
120    ///
121    /// The first of the following variables that is **set** selects the target
122    /// domain.
123    ///
124    /// - `VMI_XEN_DOMAIN`: interpreted as a numeric domain ID, falling back to
125    ///   a domain name if the value is not numeric.
126    /// - `VMI_XEN_DOMAIN_ID`: interpreted as a numeric domain ID.
127    /// - `VMI_XEN_DOMAIN_NAME`: interpreted as a domain name.
128    ///
129    /// Returns `Ok(None)` if none of the variables are set, or if the selected
130    /// variable does not resolve to a domain.
131    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    /// Sets how the driver waits for the next request after an answer.
164    ///
165    /// The default is [`VmiXenBusyWait::Adaptive`] with a `start` of 10
166    /// microseconds and a `max` of 50 microseconds.
167    pub fn with_busy_wait(mut self, busy_wait: VmiXenBusyWait) -> Self {
168        self.busy_wait = busy_wait;
169        self
170    }
171
172    /// Returns the port of the event channel that notifies the driver of
173    /// events.
174    ///
175    /// The port number belongs to the domain that runs the driver, usually
176    /// dom0.
177    pub fn event_channel_port(&self) -> u32 {
178        self.evtchn.local_port()
179    }
180
181    /// Checks the ring until a request arrives or the busy-wait time ends.
182    ///
183    /// Returns whether a request arrived. Unmasks the event channel when none
184    /// did.
185    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    /// Sleeps until Xen notifies the driver of a request or the deadline
209    /// passes.
210    ///
211    /// Afterwards the event channel stays masked while the busy-wait time is
212    /// not zero.
213    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            // Xen notified the port while it was masked, for a request that
251            // the busy wait already handled.
252            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        // As of Xen 4.22, xc_altp2m_get_mem_access() reports ESRCH for a valid
348        // GFN whose entry has not been materialized in a lazy altp2m view.
349        // Ideally, Xen would return the view's effective default access in
350        // this case. Until it does, validate the GFN through the domain p2m,
351        // discard its unrelated access value and fall back to the default
352        // access recorded for the view. Materialized Xen modes are decoded
353        // into both permissions and options.
354
355        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                // Confirm the GFN exists in the base p2m before treating ESRCH
379                // as a lazy, unmaterialized altp2m entry.
380                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            // View is destroyed automatically when it goes out of scope
522            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            // WARNING: This will change access permissions of the GFN!
545            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            // WARNING: This will change access permissions of the GFN!
560            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}