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