• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! MMIO transport for VirtIO.
2 
3 use super::{DeviceStatus, DeviceType, Transport};
4 use crate::{
5     align_up,
6     queue::Descriptor,
7     volatile::{volread, volwrite, ReadOnly, Volatile, WriteOnly},
8     Error, PhysAddr, PAGE_SIZE,
9 };
10 use core::{
11     convert::{TryFrom, TryInto},
12     fmt::{self, Display, Formatter},
13     mem::{align_of, size_of},
14     ptr::NonNull,
15 };
16 
17 const MAGIC_VALUE: u32 = 0x7472_6976;
18 pub(crate) const LEGACY_VERSION: u32 = 1;
19 pub(crate) const MODERN_VERSION: u32 = 2;
20 const CONFIG_SPACE_OFFSET: usize = 0x100;
21 
22 /// The version of the VirtIO MMIO transport supported by a device.
23 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
24 #[repr(u32)]
25 pub enum MmioVersion {
26     /// Legacy MMIO transport with page-based addressing.
27     Legacy = LEGACY_VERSION,
28     /// Modern MMIO transport.
29     Modern = MODERN_VERSION,
30 }
31 
32 impl TryFrom<u32> for MmioVersion {
33     type Error = MmioError;
34 
try_from(version: u32) -> Result<Self, Self::Error>35     fn try_from(version: u32) -> Result<Self, Self::Error> {
36         match version {
37             LEGACY_VERSION => Ok(Self::Legacy),
38             MODERN_VERSION => Ok(Self::Modern),
39             _ => Err(MmioError::UnsupportedVersion(version)),
40         }
41     }
42 }
43 
44 impl From<MmioVersion> for u32 {
from(version: MmioVersion) -> Self45     fn from(version: MmioVersion) -> Self {
46         match version {
47             MmioVersion::Legacy => LEGACY_VERSION,
48             MmioVersion::Modern => MODERN_VERSION,
49         }
50     }
51 }
52 
53 /// An error encountered initialising a VirtIO MMIO transport.
54 #[derive(Clone, Debug, Eq, PartialEq)]
55 pub enum MmioError {
56     /// The header doesn't start with the expected magic value 0x74726976.
57     BadMagic(u32),
58     /// The header reports a version number that is neither 1 (legacy) nor 2 (modern).
59     UnsupportedVersion(u32),
60     /// The header reports a device ID of 0.
61     ZeroDeviceId,
62 }
63 
64 impl Display for MmioError {
fmt(&self, f: &mut Formatter) -> fmt::Result65     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
66         match self {
67             Self::BadMagic(magic) => write!(
68                 f,
69                 "Invalid magic value {:#010x} (expected 0x74726976).",
70                 magic
71             ),
72             Self::UnsupportedVersion(version) => {
73                 write!(f, "Unsupported Virtio MMIO version {}.", version)
74             }
75             Self::ZeroDeviceId => write!(f, "Device ID was zero."),
76         }
77     }
78 }
79 
80 /// MMIO Device Register Interface, both legacy and modern.
81 ///
82 /// Ref: 4.2.2 MMIO Device Register Layout and 4.2.4 Legacy interface
83 #[repr(C)]
84 pub struct VirtIOHeader {
85     /// Magic value
86     magic: ReadOnly<u32>,
87 
88     /// Device version number
89     ///
90     /// Legacy device returns value 0x1.
91     version: ReadOnly<u32>,
92 
93     /// Virtio Subsystem Device ID
94     device_id: ReadOnly<u32>,
95 
96     /// Virtio Subsystem Vendor ID
97     vendor_id: ReadOnly<u32>,
98 
99     /// Flags representing features the device supports
100     device_features: ReadOnly<u32>,
101 
102     /// Device (host) features word selection
103     device_features_sel: WriteOnly<u32>,
104 
105     /// Reserved
106     __r1: [ReadOnly<u32>; 2],
107 
108     /// Flags representing device features understood and activated by the driver
109     driver_features: WriteOnly<u32>,
110 
111     /// Activated (guest) features word selection
112     driver_features_sel: WriteOnly<u32>,
113 
114     /// Guest page size
115     ///
116     /// The driver writes the guest page size in bytes to the register during
117     /// initialization, before any queues are used. This value should be a
118     /// power of 2 and is used by the device to calculate the Guest address
119     /// of the first queue page (see QueuePFN).
120     legacy_guest_page_size: WriteOnly<u32>,
121 
122     /// Reserved
123     __r2: ReadOnly<u32>,
124 
125     /// Virtual queue index
126     ///
127     /// Writing to this register selects the virtual queue that the following
128     /// operations on the QueueNumMax, QueueNum, QueueAlign and QueuePFN
129     /// registers apply to. The index number of the first queue is zero (0x0).
130     queue_sel: WriteOnly<u32>,
131 
132     /// Maximum virtual queue size
133     ///
134     /// Reading from the register returns the maximum size of the queue the
135     /// device is ready to process or zero (0x0) if the queue is not available.
136     /// This applies to the queue selected by writing to QueueSel and is
137     /// allowed only when QueuePFN is set to zero (0x0), so when the queue is
138     /// not actively used.
139     queue_num_max: ReadOnly<u32>,
140 
141     /// Virtual queue size
142     ///
143     /// Queue size is the number of elements in the queue. Writing to this
144     /// register notifies the device what size of the queue the driver will use.
145     /// This applies to the queue selected by writing to QueueSel.
146     queue_num: WriteOnly<u32>,
147 
148     /// Used Ring alignment in the virtual queue
149     ///
150     /// Writing to this register notifies the device about alignment boundary
151     /// of the Used Ring in bytes. This value should be a power of 2 and
152     /// applies to the queue selected by writing to QueueSel.
153     legacy_queue_align: WriteOnly<u32>,
154 
155     /// Guest physical page number of the virtual queue
156     ///
157     /// Writing to this register notifies the device about location of the
158     /// virtual queue in the Guest’s physical address space. This value is
159     /// the index number of a page starting with the queue Descriptor Table.
160     /// Value zero (0x0) means physical address zero (0x00000000) and is illegal.
161     /// When the driver stops using the queue it writes zero (0x0) to this
162     /// register. Reading from this register returns the currently used page
163     /// number of the queue, therefore a value other than zero (0x0) means that
164     /// the queue is in use. Both read and write accesses apply to the queue
165     /// selected by writing to QueueSel.
166     legacy_queue_pfn: Volatile<u32>,
167 
168     /// new interface only
169     queue_ready: Volatile<u32>,
170 
171     /// Reserved
172     __r3: [ReadOnly<u32>; 2],
173 
174     /// Queue notifier
175     queue_notify: WriteOnly<u32>,
176 
177     /// Reserved
178     __r4: [ReadOnly<u32>; 3],
179 
180     /// Interrupt status
181     interrupt_status: ReadOnly<u32>,
182 
183     /// Interrupt acknowledge
184     interrupt_ack: WriteOnly<u32>,
185 
186     /// Reserved
187     __r5: [ReadOnly<u32>; 2],
188 
189     /// Device status
190     ///
191     /// Reading from this register returns the current device status flags.
192     /// Writing non-zero values to this register sets the status flags,
193     /// indicating the OS/driver progress. Writing zero (0x0) to this register
194     /// triggers a device reset. The device sets QueuePFN to zero (0x0) for
195     /// all queues in the device. Also see 3.1 Device Initialization.
196     status: Volatile<DeviceStatus>,
197 
198     /// Reserved
199     __r6: [ReadOnly<u32>; 3],
200 
201     // new interface only since here
202     queue_desc_low: WriteOnly<u32>,
203     queue_desc_high: WriteOnly<u32>,
204 
205     /// Reserved
206     __r7: [ReadOnly<u32>; 2],
207 
208     queue_driver_low: WriteOnly<u32>,
209     queue_driver_high: WriteOnly<u32>,
210 
211     /// Reserved
212     __r8: [ReadOnly<u32>; 2],
213 
214     queue_device_low: WriteOnly<u32>,
215     queue_device_high: WriteOnly<u32>,
216 
217     /// Reserved
218     __r9: [ReadOnly<u32>; 21],
219 
220     config_generation: ReadOnly<u32>,
221 }
222 
223 impl VirtIOHeader {
224     /// Constructs a fake VirtIO header for use in unit tests.
225     #[cfg(test)]
make_fake_header( version: u32, device_id: u32, vendor_id: u32, device_features: u32, queue_num_max: u32, ) -> Self226     pub fn make_fake_header(
227         version: u32,
228         device_id: u32,
229         vendor_id: u32,
230         device_features: u32,
231         queue_num_max: u32,
232     ) -> Self {
233         Self {
234             magic: ReadOnly::new(MAGIC_VALUE),
235             version: ReadOnly::new(version),
236             device_id: ReadOnly::new(device_id),
237             vendor_id: ReadOnly::new(vendor_id),
238             device_features: ReadOnly::new(device_features),
239             device_features_sel: WriteOnly::default(),
240             __r1: Default::default(),
241             driver_features: Default::default(),
242             driver_features_sel: Default::default(),
243             legacy_guest_page_size: Default::default(),
244             __r2: Default::default(),
245             queue_sel: Default::default(),
246             queue_num_max: ReadOnly::new(queue_num_max),
247             queue_num: Default::default(),
248             legacy_queue_align: Default::default(),
249             legacy_queue_pfn: Default::default(),
250             queue_ready: Default::default(),
251             __r3: Default::default(),
252             queue_notify: Default::default(),
253             __r4: Default::default(),
254             interrupt_status: Default::default(),
255             interrupt_ack: Default::default(),
256             __r5: Default::default(),
257             status: Volatile::new(DeviceStatus::empty()),
258             __r6: Default::default(),
259             queue_desc_low: Default::default(),
260             queue_desc_high: Default::default(),
261             __r7: Default::default(),
262             queue_driver_low: Default::default(),
263             queue_driver_high: Default::default(),
264             __r8: Default::default(),
265             queue_device_low: Default::default(),
266             queue_device_high: Default::default(),
267             __r9: Default::default(),
268             config_generation: Default::default(),
269         }
270     }
271 }
272 
273 /// MMIO Device Register Interface.
274 ///
275 /// Ref: 4.2.2 MMIO Device Register Layout and 4.2.4 Legacy interface
276 #[derive(Debug)]
277 pub struct MmioTransport {
278     header: NonNull<VirtIOHeader>,
279     version: MmioVersion,
280 }
281 
282 impl MmioTransport {
283     /// Constructs a new VirtIO MMIO transport, or returns an error if the header reports an
284     /// unsupported version.
285     ///
286     /// # Safety
287     /// `header` must point to a properly aligned valid VirtIO MMIO region, which must remain valid
288     /// for the lifetime of the transport that is returned.
new(header: NonNull<VirtIOHeader>) -> Result<Self, MmioError>289     pub unsafe fn new(header: NonNull<VirtIOHeader>) -> Result<Self, MmioError> {
290         let magic = volread!(header, magic);
291         if magic != MAGIC_VALUE {
292             return Err(MmioError::BadMagic(magic));
293         }
294         if volread!(header, device_id) == 0 {
295             return Err(MmioError::ZeroDeviceId);
296         }
297         let version = volread!(header, version).try_into()?;
298         Ok(Self { header, version })
299     }
300 
301     /// Gets the version of the VirtIO MMIO transport.
version(&self) -> MmioVersion302     pub fn version(&self) -> MmioVersion {
303         self.version
304     }
305 
306     /// Gets the vendor ID.
vendor_id(&self) -> u32307     pub fn vendor_id(&self) -> u32 {
308         // Safe because self.header points to a valid VirtIO MMIO region.
309         unsafe { volread!(self.header, vendor_id) }
310     }
311 }
312 
313 impl Transport for MmioTransport {
device_type(&self) -> DeviceType314     fn device_type(&self) -> DeviceType {
315         // Safe because self.header points to a valid VirtIO MMIO region.
316         let device_id = unsafe { volread!(self.header, device_id) };
317         device_id.into()
318     }
319 
read_device_features(&mut self) -> u64320     fn read_device_features(&mut self) -> u64 {
321         // Safe because self.header points to a valid VirtIO MMIO region.
322         unsafe {
323             volwrite!(self.header, device_features_sel, 0); // device features [0, 32)
324             let mut device_features_bits = volread!(self.header, device_features).into();
325             volwrite!(self.header, device_features_sel, 1); // device features [32, 64)
326             device_features_bits += (volread!(self.header, device_features) as u64) << 32;
327             device_features_bits
328         }
329     }
330 
write_driver_features(&mut self, driver_features: u64)331     fn write_driver_features(&mut self, driver_features: u64) {
332         // Safe because self.header points to a valid VirtIO MMIO region.
333         unsafe {
334             volwrite!(self.header, driver_features_sel, 0); // driver features [0, 32)
335             volwrite!(self.header, driver_features, driver_features as u32);
336             volwrite!(self.header, driver_features_sel, 1); // driver features [32, 64)
337             volwrite!(self.header, driver_features, (driver_features >> 32) as u32);
338         }
339     }
340 
max_queue_size(&self) -> u32341     fn max_queue_size(&self) -> u32 {
342         // Safe because self.header points to a valid VirtIO MMIO region.
343         unsafe { volread!(self.header, queue_num_max) }
344     }
345 
notify(&mut self, queue: u16)346     fn notify(&mut self, queue: u16) {
347         // Safe because self.header points to a valid VirtIO MMIO region.
348         unsafe {
349             volwrite!(self.header, queue_notify, queue.into());
350         }
351     }
352 
get_status(&self) -> DeviceStatus353     fn get_status(&self) -> DeviceStatus {
354         // Safe because self.header points to a valid VirtIO MMIO region.
355         unsafe { volread!(self.header, status) }
356     }
357 
set_status(&mut self, status: DeviceStatus)358     fn set_status(&mut self, status: DeviceStatus) {
359         // Safe because self.header points to a valid VirtIO MMIO region.
360         unsafe {
361             volwrite!(self.header, status, status);
362         }
363     }
364 
set_guest_page_size(&mut self, guest_page_size: u32)365     fn set_guest_page_size(&mut self, guest_page_size: u32) {
366         match self.version {
367             MmioVersion::Legacy => {
368                 // Safe because self.header points to a valid VirtIO MMIO region.
369                 unsafe {
370                     volwrite!(self.header, legacy_guest_page_size, guest_page_size);
371                 }
372             }
373             MmioVersion::Modern => {
374                 // No-op, modern devices don't care.
375             }
376         }
377     }
378 
requires_legacy_layout(&self) -> bool379     fn requires_legacy_layout(&self) -> bool {
380         match self.version {
381             MmioVersion::Legacy => true,
382             MmioVersion::Modern => false,
383         }
384     }
385 
queue_set( &mut self, queue: u16, size: u32, descriptors: PhysAddr, driver_area: PhysAddr, device_area: PhysAddr, )386     fn queue_set(
387         &mut self,
388         queue: u16,
389         size: u32,
390         descriptors: PhysAddr,
391         driver_area: PhysAddr,
392         device_area: PhysAddr,
393     ) {
394         match self.version {
395             MmioVersion::Legacy => {
396                 assert_eq!(
397                     driver_area - descriptors,
398                     size_of::<Descriptor>() * size as usize
399                 );
400                 assert_eq!(
401                     device_area - descriptors,
402                     align_up(
403                         size_of::<Descriptor>() * size as usize
404                             + size_of::<u16>() * (size as usize + 3)
405                     )
406                 );
407                 let align = PAGE_SIZE as u32;
408                 let pfn = (descriptors / PAGE_SIZE) as u32;
409                 assert_eq!(pfn as usize * PAGE_SIZE, descriptors);
410                 // Safe because self.header points to a valid VirtIO MMIO region.
411                 unsafe {
412                     volwrite!(self.header, queue_sel, queue.into());
413                     volwrite!(self.header, queue_num, size);
414                     volwrite!(self.header, legacy_queue_align, align);
415                     volwrite!(self.header, legacy_queue_pfn, pfn);
416                 }
417             }
418             MmioVersion::Modern => {
419                 // Safe because self.header points to a valid VirtIO MMIO region.
420                 unsafe {
421                     volwrite!(self.header, queue_sel, queue.into());
422                     volwrite!(self.header, queue_num, size);
423                     volwrite!(self.header, queue_desc_low, descriptors as u32);
424                     volwrite!(self.header, queue_desc_high, (descriptors >> 32) as u32);
425                     volwrite!(self.header, queue_driver_low, driver_area as u32);
426                     volwrite!(self.header, queue_driver_high, (driver_area >> 32) as u32);
427                     volwrite!(self.header, queue_device_low, device_area as u32);
428                     volwrite!(self.header, queue_device_high, (device_area >> 32) as u32);
429                     volwrite!(self.header, queue_ready, 1);
430                 }
431             }
432         }
433     }
434 
queue_unset(&mut self, queue: u16)435     fn queue_unset(&mut self, queue: u16) {
436         match self.version {
437             MmioVersion::Legacy => {
438                 // Safe because self.header points to a valid VirtIO MMIO region.
439                 unsafe {
440                     volwrite!(self.header, queue_sel, queue.into());
441                     volwrite!(self.header, queue_num, 0);
442                     volwrite!(self.header, legacy_queue_align, 0);
443                     volwrite!(self.header, legacy_queue_pfn, 0);
444                 }
445             }
446             MmioVersion::Modern => {
447                 // Safe because self.header points to a valid VirtIO MMIO region.
448                 unsafe {
449                     volwrite!(self.header, queue_sel, queue.into());
450 
451                     volwrite!(self.header, queue_ready, 0);
452                     // Wait until we read the same value back, to ensure synchronisation (see 4.2.2.2).
453                     while volread!(self.header, queue_ready) != 0 {}
454 
455                     volwrite!(self.header, queue_num, 0);
456                     volwrite!(self.header, queue_desc_low, 0);
457                     volwrite!(self.header, queue_desc_high, 0);
458                     volwrite!(self.header, queue_driver_low, 0);
459                     volwrite!(self.header, queue_driver_high, 0);
460                     volwrite!(self.header, queue_device_low, 0);
461                     volwrite!(self.header, queue_device_high, 0);
462                 }
463             }
464         }
465     }
466 
queue_used(&mut self, queue: u16) -> bool467     fn queue_used(&mut self, queue: u16) -> bool {
468         // Safe because self.header points to a valid VirtIO MMIO region.
469         unsafe {
470             volwrite!(self.header, queue_sel, queue.into());
471             match self.version {
472                 MmioVersion::Legacy => volread!(self.header, legacy_queue_pfn) != 0,
473                 MmioVersion::Modern => volread!(self.header, queue_ready) != 0,
474             }
475         }
476     }
477 
ack_interrupt(&mut self) -> bool478     fn ack_interrupt(&mut self) -> bool {
479         // Safe because self.header points to a valid VirtIO MMIO region.
480         unsafe {
481             let interrupt = volread!(self.header, interrupt_status);
482             if interrupt != 0 {
483                 volwrite!(self.header, interrupt_ack, interrupt);
484                 true
485             } else {
486                 false
487             }
488         }
489     }
490 
config_space<T>(&self) -> Result<NonNull<T>, Error>491     fn config_space<T>(&self) -> Result<NonNull<T>, Error> {
492         if align_of::<T>() > 4 {
493             // Panic as this should only happen if the driver is written incorrectly.
494             panic!(
495                 "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.",
496                 align_of::<T>()
497             );
498         }
499         Ok(NonNull::new((self.header.as_ptr() as usize + CONFIG_SPACE_OFFSET) as _).unwrap())
500     }
501 }
502 
503 impl Drop for MmioTransport {
drop(&mut self)504     fn drop(&mut self) {
505         // Reset the device when the transport is dropped.
506         self.set_status(DeviceStatus::empty())
507     }
508 }
509