devices/virtio/iommu/sys/linux/
vfio_wrapper.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Wraps VfioContainer for virtio-iommu implementation
6
7use std::sync::Arc;
8
9use anyhow::Context;
10use base::AsRawDescriptor;
11use base::AsRawDescriptors;
12use base::Protection;
13use base::RawDescriptor;
14use sync::Mutex;
15use vm_memory::GuestAddress;
16use vm_memory::GuestMemory;
17
18use crate::vfio::VfioError;
19use crate::virtio::iommu::memory_mapper::AddMapResult;
20use crate::virtio::iommu::memory_mapper::MappingInfo;
21use crate::virtio::iommu::memory_mapper::MemoryMapper;
22use crate::virtio::iommu::memory_mapper::RemoveMapResult;
23use crate::VfioContainer;
24
25pub struct VfioWrapper {
26    container: Arc<Mutex<VfioContainer>>,
27    // ID of the VFIO group which constitutes the container. Note that we rely on
28    // the fact that no container contains multiple groups.
29    id: u32,
30    mem: GuestMemory,
31}
32
33impl VfioWrapper {
34    pub fn new(container: Arc<Mutex<VfioContainer>>, mem: GuestMemory) -> Self {
35        let c = container.lock();
36        let groups = c.group_ids();
37        // NOTE: vfio_get_container ensures each group gets its own container.
38        assert!(groups.len() == 1);
39        let id = *groups[0];
40        drop(c);
41        Self { container, id, mem }
42    }
43
44    pub fn new_with_id(container: VfioContainer, id: u32, mem: GuestMemory) -> Self {
45        Self {
46            container: Arc::new(Mutex::new(container)),
47            id,
48            mem,
49        }
50    }
51
52    pub fn clone_as_raw_descriptor(&self) -> Result<RawDescriptor, VfioError> {
53        self.container.lock().clone_as_raw_descriptor()
54    }
55
56    unsafe fn do_map(&self, map: MappingInfo) -> anyhow::Result<AddMapResult> {
57        let res = self.container.lock().vfio_dma_map(
58            map.iova,
59            map.size,
60            map.gpa.offset(),
61            map.prot.allows(&Protection::write()),
62        );
63        if let Err(VfioError::IommuDmaMap(err)) = res {
64            if err.errno() == libc::EEXIST {
65                // A mapping already exists in the requested range,
66                return Ok(AddMapResult::OverlapFailure);
67            }
68        }
69        res.context("vfio mapping error").map(|_| AddMapResult::Ok)
70    }
71}
72
73impl MemoryMapper for VfioWrapper {
74    fn add_map(&mut self, mut map: MappingInfo) -> anyhow::Result<AddMapResult> {
75        map.gpa = GuestAddress(
76            self.mem
77                .get_host_address_range(map.gpa, map.size as usize)
78                .context("failed to find host address")? as u64,
79        );
80
81        // SAFETY:
82        // Safe because both guest and host address are guaranteed by
83        // get_host_address_range() to be valid.
84        unsafe { self.do_map(map) }
85    }
86
87    unsafe fn vfio_dma_map(
88        &mut self,
89        iova: u64,
90        hva: u64,
91        size: u64,
92        prot: Protection,
93    ) -> anyhow::Result<AddMapResult> {
94        self.do_map(MappingInfo {
95            iova,
96            gpa: GuestAddress(hva),
97            size,
98            prot,
99        })
100    }
101
102    fn remove_map(&mut self, iova_start: u64, size: u64) -> anyhow::Result<RemoveMapResult> {
103        iova_start.checked_add(size).context("iova overflow")?;
104        self.container
105            .lock()
106            .vfio_dma_unmap(iova_start, size)
107            .context("vfio unmapping error")
108            .map(|_| RemoveMapResult::Success(None))
109    }
110
111    fn get_mask(&self) -> anyhow::Result<u64> {
112        self.container
113            .lock()
114            .vfio_get_iommu_page_size_mask()
115            .context("vfio get mask error")
116    }
117
118    fn supports_detach(&self) -> bool {
119        // A few reasons why we don't support detach:
120        //
121        // 1. Seems it's not possible to dynamically attach and detach a IOMMU domain if the virtio
122        //    IOMMU device is running on top of VFIO
123        // 2. Even if VIRTIO_IOMMU_T_DETACH is implemented in front-end driver, it could violate the
124        //    following virtio IOMMU spec: Detach an endpoint from a domain. when this request
125        //    completes, the endpoint cannot access any mapping from that domain anymore.
126        //
127        //    This is because VFIO doesn't support detaching a single device. When the virtio-iommu
128        //    device receives a VIRTIO_IOMMU_T_DETACH request, it can either to:
129        //    - detach a group: any other endpoints in the group lose access to the domain.
130        //    - do not detach the group at all: this breaks the above mentioned spec.
131        false
132    }
133
134    fn id(&self) -> u32 {
135        self.id
136    }
137}
138
139impl AsRawDescriptors for VfioWrapper {
140    fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
141        vec![self.container.lock().as_raw_descriptor()]
142    }
143}