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