• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Implement a userspace PCI device driver for the virtio vhost-user device.
6 
7 use std::str::FromStr;
8 use std::sync::Arc;
9 use std::time::{Duration, Instant};
10 
11 use anyhow::{anyhow, bail, Context, Result};
12 use base::{info, Event};
13 use data_model::DataInit;
14 use memoffset::offset_of;
15 use resources::Alloc;
16 use vfio_sys::*;
17 use virtio_sys::vhost::VIRTIO_F_VERSION_1;
18 
19 use crate::pci::{MsixCap, PciAddress, PciCapabilityID, CAPABILITY_LIST_HEAD_OFFSET};
20 use crate::vfio::{VfioDevice, VfioPciConfig, VfioRegionAddr};
21 use crate::virtio::vhost::user::device::vvu::{
22     bus::open_vfio_device,
23     queue::{DescTableAddrs, IovaAllocator, UserQueue},
24 };
25 use crate::virtio::{PciCapabilityType, VirtioPciCap};
26 
27 const VIRTIO_CONFIG_STATUS_RESET: u8 = 0;
28 
get_pci_cap_addr(cap: &VirtioPciCap) -> Result<VfioRegionAddr>29 fn get_pci_cap_addr(cap: &VirtioPciCap) -> Result<VfioRegionAddr> {
30     const PCI_MAX_RESOURCE: u8 = 6;
31 
32     if cap.bar >= PCI_MAX_RESOURCE {
33         bail!("invalid bar: {:?} >= {}", cap.bar, PCI_MAX_RESOURCE);
34     }
35 
36     if u32::from(cap.offset)
37         .checked_add(u32::from(cap.length))
38         .is_none()
39     {
40         bail!("overflow: {:?} + {:?}", cap.offset, cap.length);
41     }
42 
43     Ok(VfioRegionAddr {
44         index: cap.bar.into(),
45         addr: u32::from(cap.offset) as u64,
46     })
47 }
48 
49 #[repr(C)]
50 #[derive(Debug, Default, Copy, Clone)]
51 /// VirtIO spec: 4.1.4.3 Common configuration structure layout
52 struct virtio_pci_common_cfg {
53     // For the whole device.
54     device_feature_select: u32,
55     device_feature: u32,
56     guest_feature_select: u32,
57     guest_feature: u32,
58     msix_config: u16,
59     num_queues: u16,
60     device_status: u8,
61     config_generation: u8,
62 
63     // For a specific virtqueue.
64     queue_select: u16,
65     queue_size: u16,
66     queue_msix_vector: u16,
67     queue_enable: u16,
68     queue_notify_off: u16,
69     queue_desc_lo: u32,
70     queue_desc_hi: u32,
71     queue_avail_lo: u32,
72     queue_avail_hi: u32,
73     queue_used_lo: u32,
74     queue_used_hi: u32,
75 }
76 
77 unsafe impl DataInit for virtio_pci_common_cfg {}
78 
79 #[repr(C)]
80 #[derive(Debug, Default, Copy, Clone)]
81 struct virtio_pci_notification_cfg {
82     notification_select: u16,
83     notification_msix_vector: u16,
84 }
85 
86 unsafe impl DataInit for virtio_pci_notification_cfg {}
87 
88 #[derive(Clone)]
89 pub struct VvuPciCaps {
90     msix_table_size: u16,
91     common_cfg_addr: VfioRegionAddr,
92     notify_off_multiplier: u32,
93     notify_base_addr: VfioRegionAddr,
94     dev_cfg_addr: VfioRegionAddr,
95     isr_addr: VfioRegionAddr,
96     doorbell_off_multiplier: u32,
97     doorbell_base_addr: VfioRegionAddr,
98     notify_cfg_addr: VfioRegionAddr,
99     shared_mem_cfg_addr: VfioRegionAddr,
100 }
101 
102 impl VvuPciCaps {
new(config: &VfioPciConfig) -> Result<Self>103     pub fn new(config: &VfioPciConfig) -> Result<Self> {
104         // Safe because zero is valid for every field in `VvuPciCaps`.
105         let mut caps: Self = unsafe { std::mem::zeroed() };
106 
107         // Read PCI capability config one by one and set up each of them.
108         let mut pos: u8 = config.read_config(CAPABILITY_LIST_HEAD_OFFSET as u32);
109         while pos != 0 {
110             let cfg: [u8; 2] = config.read_config(pos.into());
111             let (cap_id, cap_next) = (cfg[0], cfg[1]);
112 
113             if cap_id == PciCapabilityID::Msix as u8 {
114                 let cap = config.read_config::<MsixCap>(pos.into());
115                 // According to PCI 3.0 specification section 6.8.2.3 ("Message Control for MSI-X"),
116                 // MSI-X Table Size N, which is encoded as N-1.
117                 caps.msix_table_size = cap.msg_ctl().get_table_size() + 1;
118             }
119 
120             if cap_id != PciCapabilityID::VendorSpecific as u8 {
121                 pos = cap_next;
122                 continue;
123             }
124 
125             let cap: VirtioPciCap = config.read_config(pos.into());
126 
127             let cfg = PciCapabilityType::n(cap.cfg_type)
128                 .ok_or_else(|| anyhow!("invalid cfg_type: {}", cap.cfg_type))?;
129             match cfg {
130                 PciCapabilityType::CommonConfig => {
131                     caps.common_cfg_addr = get_pci_cap_addr(&cap)?;
132                 }
133                 PciCapabilityType::NotifyConfig => {
134                     caps.notify_off_multiplier =
135                         config.read_config(pos as u32 + std::mem::size_of::<VirtioPciCap>() as u32);
136                     caps.notify_base_addr = get_pci_cap_addr(&cap)?;
137                 }
138                 PciCapabilityType::IsrConfig => {
139                     caps.isr_addr = get_pci_cap_addr(&cap)?;
140                 }
141                 PciCapabilityType::DeviceConfig => {
142                     caps.dev_cfg_addr = get_pci_cap_addr(&cap)?;
143                 }
144                 PciCapabilityType::PciConfig => {
145                     // do nothing
146                 }
147                 PciCapabilityType::DoorbellConfig => {
148                     caps.doorbell_off_multiplier =
149                         config.read_config(pos as u32 + std::mem::size_of::<VirtioPciCap>() as u32);
150                     caps.doorbell_base_addr = get_pci_cap_addr(&cap)?;
151                 }
152                 PciCapabilityType::NotificationConfig => {
153                     caps.notify_cfg_addr = get_pci_cap_addr(&cap)?;
154                 }
155                 PciCapabilityType::SharedMemoryConfig => {
156                     caps.shared_mem_cfg_addr = get_pci_cap_addr(&cap)?;
157                 }
158             }
159 
160             pos = cap.cap_next;
161         }
162 
163         Ok(caps)
164     }
165 
doorbell_off_multiplier(&self) -> u32166     pub fn doorbell_off_multiplier(&self) -> u32 {
167         self.doorbell_off_multiplier
168     }
169 
doorbell_base_addr(&self) -> &VfioRegionAddr170     pub fn doorbell_base_addr(&self) -> &VfioRegionAddr {
171         &self.doorbell_base_addr
172     }
173 
shared_mem_cfg_addr(&self) -> &VfioRegionAddr174     pub fn shared_mem_cfg_addr(&self) -> &VfioRegionAddr {
175         &self.shared_mem_cfg_addr
176     }
177 }
178 
179 macro_rules! write_common_cfg_field {
180     ($device:expr, $field:ident, $val:expr) => {
181         $device.vfio_dev.region_write_to_addr(
182             &$val,
183             &$device.caps.common_cfg_addr,
184             offset_of!(virtio_pci_common_cfg, $field) as u64,
185         )
186     };
187 }
188 
189 macro_rules! read_common_cfg_field {
190     ($device:expr,  $field:ident) => {
191         $device.vfio_dev.region_read_from_addr(
192             &$device.caps.common_cfg_addr,
193             offset_of!(virtio_pci_common_cfg, $field) as u64,
194         )
195     };
196 }
197 
198 macro_rules! write_notify_cfg_field {
199     ($device:expr, $field:ident, $val:expr) => {
200         $device.vfio_dev.region_write_to_addr(
201             &$val,
202             &$device.caps.notify_cfg_addr,
203             offset_of!(virtio_pci_notification_cfg, $field) as u64,
204         )
205     };
206 }
207 
208 macro_rules! read_notify_cfg_field {
209     ($device:expr,  $field:ident) => {
210         $device.vfio_dev.region_read_from_addr(
211             &$device.caps.notify_cfg_addr,
212             offset_of!(virtio_pci_notification_cfg, $field) as u64,
213         )
214     };
215 }
216 
217 /// A wrapper of VVU's notification resource which works as an interrupt for a virtqueue.
218 pub struct QueueNotifier(VfioRegionAddr);
219 
220 impl QueueNotifier {
notify(&self, vfio_dev: &VfioDevice, index: u16)221     pub fn notify(&self, vfio_dev: &VfioDevice, index: u16) {
222         vfio_dev.region_write_to_addr(&index, &self.0, 0);
223     }
224 }
225 
226 pub struct VvuPciDevice {
227     pub vfio_dev: Arc<VfioDevice>,
228     pub caps: VvuPciCaps,
229     pub queues: Vec<UserQueue>,
230     pub queue_notifiers: Vec<QueueNotifier>,
231     pub irqs: Vec<Event>,
232     pub notification_evts: Vec<Event>,
233 }
234 
235 #[derive(Debug, Clone, Copy)]
236 pub enum QueueType {
237     Rx = 0, // the integer represents the queue index.
238     Tx = 1,
239 }
240 
241 impl VvuPciDevice {
242     /// Creates a driver for virtio-vhost-user PCI device.
243     ///
244     /// # Arguments
245     ///
246     /// * `pci_id` - PCI device ID such as `"0000:00:05.0"`.
247     /// * `device_vq_num` - number of virtqueues that the device backend (e.g. block) may use.
new(pci_id: &str, device_vq_num: usize) -> Result<Self>248     pub fn new(pci_id: &str, device_vq_num: usize) -> Result<Self> {
249         let pci_address = PciAddress::from_str(pci_id).context("failed to parse PCI address")?;
250         let vfio_dev = Arc::new(open_vfio_device(pci_address)?);
251         let config = VfioPciConfig::new(vfio_dev.clone());
252         let caps = VvuPciCaps::new(&config)?;
253         vfio_dev
254             .check_device_info()
255             .context("failed to check VFIO device information")?;
256 
257         let page_mask = vfio_dev
258             .vfio_get_iommu_page_size_mask()
259             .context("failed to get iommu page size mask")?;
260         if page_mask & (base::pagesize() as u64) == 0 {
261             bail!("Unsupported iommu page mask {:x}", page_mask);
262         }
263 
264         let mut pci_dev = Self {
265             vfio_dev,
266             caps,
267             queues: vec![],
268             queue_notifiers: vec![],
269             irqs: vec![],
270             notification_evts: vec![],
271         };
272 
273         config.set_bus_master();
274         pci_dev.init(device_vq_num)?;
275 
276         Ok(pci_dev)
277     }
278 
set_status(&self, status: u8)279     fn set_status(&self, status: u8) {
280         let new_status = if status == VIRTIO_CONFIG_STATUS_RESET {
281             VIRTIO_CONFIG_STATUS_RESET
282         } else {
283             let cur_status: u8 = read_common_cfg_field!(self, device_status);
284             status | cur_status
285         };
286 
287         write_common_cfg_field!(self, device_status, new_status);
288     }
289 
get_device_feature(&self) -> u64290     fn get_device_feature(&self) -> u64 {
291         write_common_cfg_field!(self, device_feature_select, 0);
292         let lower: u32 = read_common_cfg_field!(self, device_feature);
293         write_common_cfg_field!(self, device_feature_select, 1);
294         let upper: u32 = read_common_cfg_field!(self, device_feature);
295 
296         lower as u64 | ((upper as u64) << 32)
297     }
298 
set_device_feature(&self, features: u64)299     fn set_device_feature(&self, features: u64) {
300         let lower: u32 = (features & (u32::MAX as u64)) as u32;
301         let upper: u32 = (features >> 32) as u32;
302         write_common_cfg_field!(self, device_feature_select, 0);
303         write_common_cfg_field!(self, device_feature, lower);
304         write_common_cfg_field!(self, device_feature_select, 1);
305         write_common_cfg_field!(self, device_feature, upper);
306     }
307 
308     /// Creates the VVU's virtqueue (i.e. rxq or txq).
create_queue(&self, typ: QueueType) -> Result<(UserQueue, QueueNotifier)>309     fn create_queue(&self, typ: QueueType) -> Result<(UserQueue, QueueNotifier)> {
310         write_common_cfg_field!(self, queue_select, typ as u16);
311 
312         let queue_size: u16 = read_common_cfg_field!(self, queue_size);
313         if queue_size == 0 {
314             bail!("queue_size for {:?} queue is 0", typ);
315         }
316 
317         let device_writable = match typ {
318             QueueType::Rx => true,
319             QueueType::Tx => false,
320         };
321         let queue = UserQueue::new(queue_size, device_writable, typ as u8, self)?;
322         let DescTableAddrs { desc, avail, used } = queue.desc_table_addrs()?;
323 
324         let desc_lo = (desc & 0xffffffff) as u32;
325         let desc_hi = (desc >> 32) as u32;
326         write_common_cfg_field!(self, queue_desc_lo, desc_lo);
327         write_common_cfg_field!(self, queue_desc_hi, desc_hi);
328 
329         let avail_lo = (avail & 0xffffffff) as u32;
330         let avail_hi = (avail >> 32) as u32;
331         write_common_cfg_field!(self, queue_avail_lo, avail_lo);
332         write_common_cfg_field!(self, queue_avail_hi, avail_hi);
333 
334         let used_lo = (used & 0xffffffff) as u32;
335         let used_hi = (used >> 32) as u32;
336         write_common_cfg_field!(self, queue_used_lo, used_lo);
337         write_common_cfg_field!(self, queue_used_hi, used_hi);
338 
339         let notify_off: u16 = read_common_cfg_field!(self, queue_notify_off);
340         let mut notify_addr = self.caps.notify_base_addr.clone();
341         notify_addr.addr += notify_off as u64 * self.caps.notify_off_multiplier as u64;
342         let notifier = QueueNotifier(notify_addr);
343 
344         write_common_cfg_field!(self, queue_enable, 1_u16);
345 
346         Ok((queue, notifier))
347     }
348 
349     /// Creates the VVU's rxq and txq.
create_queues(&self) -> Result<(Vec<UserQueue>, Vec<QueueNotifier>)>350     fn create_queues(&self) -> Result<(Vec<UserQueue>, Vec<QueueNotifier>)> {
351         let (rxq, rxq_notifier) = self.create_queue(QueueType::Rx)?;
352         rxq_notifier.notify(&self.vfio_dev, QueueType::Rx as u16);
353 
354         let (txq, txq_notifier) = self.create_queue(QueueType::Tx)?;
355         txq_notifier.notify(&self.vfio_dev, QueueType::Tx as u16);
356 
357         Ok((vec![rxq, txq], vec![rxq_notifier, txq_notifier]))
358     }
359 
360     /// Creates two sets of interrupts events; ones for the VVU virtqueues (i.e. rxq and txq) and
361     /// ones for the device virtqueues.
362     ///
363     /// # Arguments
364     /// * `device_vq_num` - the number of queues for the device.
create_irqs(&self, device_vq_num: usize) -> Result<(Vec<Event>, Vec<Event>)>365     fn create_irqs(&self, device_vq_num: usize) -> Result<(Vec<Event>, Vec<Event>)> {
366         const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff;
367 
368         // Sets msix_config
369         write_common_cfg_field!(self, msix_config, 0u16);
370         let v: u16 = read_common_cfg_field!(self, msix_config);
371         if v == VIRTIO_MSI_NO_VECTOR {
372             bail!("failed to set config vector: {}", v);
373         }
374 
375         // Creates events for the interrupts of vvu's rxq and txq.
376         let vvu_irqs = vec![
377             Event::new().context("failed to create event")?,
378             Event::new().context("failed to create event")?,
379         ];
380 
381         // Create events for the device virtqueue interrupts.
382         let mut notification_evts = Vec::with_capacity(device_vq_num);
383         for _ in 0..device_vq_num {
384             notification_evts.push(Event::new().context("failed to create event")?);
385         }
386 
387         let msix_num = 2 + device_vq_num;
388         if msix_num > usize::from(self.caps.msix_table_size) {
389             bail!(
390                 "{} MSI-X vector is required but only {} are available.",
391                 msix_num,
392                 self.caps.msix_table_size
393             );
394         }
395 
396         let mut msix_vec = Vec::with_capacity(msix_num);
397         msix_vec.push(Some(&vvu_irqs[0]));
398         msix_vec.push(Some(&vvu_irqs[1]));
399         msix_vec.extend(notification_evts.iter().take(device_vq_num).map(Some));
400 
401         self.vfio_dev
402             .irq_enable(&msix_vec, VFIO_PCI_MSIX_IRQ_INDEX, 0)
403             .map_err(|e| anyhow!("failed to enable irq: {}", e))?;
404 
405         // Registers VVU virtqueue's irqs by writing `queue_msix_vector`.
406         for index in 0..self.queues.len() {
407             write_common_cfg_field!(self, queue_select, index as u16);
408             write_common_cfg_field!(self, queue_msix_vector, index as u16);
409             let v: u16 = read_common_cfg_field!(self, queue_msix_vector);
410             if v == VIRTIO_MSI_NO_VECTOR {
411                 bail!("failed to set vector {} to {}-th vvu virtqueue", v, index);
412             }
413         }
414 
415         // Registers the device virtqueus's irqs by writing `notification_msix_vector`.
416         for i in 0..device_vq_num as u16 {
417             let msix_vector = self.queues.len() as u16 + i;
418 
419             write_notify_cfg_field!(self, notification_select, i);
420             let select: u16 = read_notify_cfg_field!(self, notification_select);
421             if select != i {
422                 bail!("failed to select {}-th notification", i);
423             }
424 
425             write_notify_cfg_field!(self, notification_msix_vector, msix_vector);
426             let vector: u16 = read_notify_cfg_field!(self, notification_msix_vector);
427             if msix_vector != vector {
428                 bail!(
429                     "failed to set vector {} to {}-th notification",
430                     msix_vector,
431                     i
432                 );
433             }
434         }
435 
436         Ok((vvu_irqs, notification_evts))
437     }
438 
init(&mut self, device_vq_num: usize) -> Result<()>439     fn init(&mut self, device_vq_num: usize) -> Result<()> {
440         self.set_status(VIRTIO_CONFIG_STATUS_RESET as u8);
441         // Wait until reset is done with timeout.
442         let deadline = Instant::now() + Duration::from_secs(1);
443         loop {
444             let cur_status: u8 = read_common_cfg_field!(self, device_status);
445             if cur_status == 0 {
446                 break;
447             }
448             if Instant::now() < deadline {
449                 std::thread::sleep(Duration::from_millis(10));
450             } else {
451                 bail!("device initialization didn't finish within the time limit");
452             }
453         }
454 
455         self.set_status(
456             (virtio_sys::vhost::VIRTIO_CONFIG_S_ACKNOWLEDGE
457                 | virtio_sys::vhost::VIRTIO_CONFIG_S_DRIVER) as u8,
458         );
459 
460         // TODO(b/207364742): Support VIRTIO_RING_F_EVENT_IDX.
461         let required_features = 1u64 << VIRTIO_F_VERSION_1;
462         self.set_device_feature(required_features);
463         let enabled_features = self.get_device_feature();
464         if (required_features & enabled_features) != required_features {
465             bail!(
466                 "required feature set is 0x{:x} but 0x{:x} is enabled",
467                 required_features,
468                 enabled_features
469             );
470         };
471         self.set_status(virtio_sys::vhost::VIRTIO_CONFIG_S_FEATURES_OK as u8);
472 
473         // Initialize Virtqueues
474         let (queues, queue_notifiers) = self.create_queues()?;
475         self.queues = queues;
476         self.queue_notifiers = queue_notifiers;
477 
478         let (irqs, notification_evts) = self.create_irqs(device_vq_num)?;
479         self.irqs = irqs;
480         self.notification_evts = notification_evts;
481 
482         self.set_status(virtio_sys::vhost::VIRTIO_CONFIG_S_DRIVER_OK as u8);
483 
484         Ok(())
485     }
486 
start(&self) -> Result<()>487     pub fn start(&self) -> Result<()> {
488         const STATUS_OFFSET: u64 = 0;
489         const VIRTIO_VHOST_USER_STATUS_SLAVE_UP: usize = 0;
490         let mut status: u32 = self
491             .vfio_dev
492             .region_read_from_addr(&self.caps.dev_cfg_addr, STATUS_OFFSET);
493 
494         status |= 1u32 << VIRTIO_VHOST_USER_STATUS_SLAVE_UP;
495 
496         self.vfio_dev
497             .region_write_to_addr(&status, &self.caps.dev_cfg_addr, STATUS_OFFSET);
498 
499         info!("vvu device started");
500         Ok(())
501     }
502 }
503 
504 impl IovaAllocator for VvuPciDevice {
alloc_iova(&self, size: u64, tag: u8) -> Result<u64>505     fn alloc_iova(&self, size: u64, tag: u8) -> Result<u64> {
506         self.vfio_dev
507             .alloc_iova(size, base::pagesize() as u64, Alloc::VvuQueue(tag))
508             .context("failed to find an iova region to map the gpa region to")
509     }
510 
map_iova(&self, iova: u64, size: u64, addr: *const u8) -> Result<()>511     unsafe fn map_iova(&self, iova: u64, size: u64, addr: *const u8) -> Result<()> {
512         self.vfio_dev
513             .vfio_dma_map(iova, size, addr as u64, true)
514             .context("failed to map iova")
515     }
516 }
517