devices/virtio/
virtio_mmio_device.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
5use std::collections::BTreeMap;
6
7use acpi_tables::aml;
8use acpi_tables::aml::Aml;
9use anyhow::anyhow;
10use anyhow::Context;
11use base::error;
12use base::pagesize;
13use base::warn;
14use base::AsRawDescriptors;
15use base::Event;
16use base::RawDescriptor;
17use base::Result;
18use hypervisor::Datamatch;
19use resources::AllocOptions;
20use resources::SystemAllocator;
21use virtio_sys::virtio_config::VIRTIO_CONFIG_S_ACKNOWLEDGE;
22use virtio_sys::virtio_config::VIRTIO_CONFIG_S_DRIVER;
23use virtio_sys::virtio_config::VIRTIO_CONFIG_S_DRIVER_OK;
24use virtio_sys::virtio_config::VIRTIO_CONFIG_S_FAILED;
25use virtio_sys::virtio_config::VIRTIO_CONFIG_S_FEATURES_OK;
26use virtio_sys::virtio_config::VIRTIO_CONFIG_S_NEEDS_RESET;
27use virtio_sys::virtio_mmio::*;
28use vm_control::DeviceId;
29use vm_control::PlatformDeviceId;
30use vm_memory::GuestMemory;
31
32use super::*;
33use crate::BusAccessInfo;
34use crate::BusDevice;
35use crate::BusDeviceObj;
36use crate::IrqEdgeEvent;
37use crate::Suspendable;
38
39const VIRT_MAGIC: u32 = 0x74726976; /* 'virt' */
40const VIRT_VERSION: u8 = 2;
41const VIRT_VENDOR: u32 = 0x4D565243; /* 'CRVM' */
42const VIRTIO_MMIO_REGION_SZ: u64 = 0x200;
43
44/// Implements the
45/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
46/// transport for virtio devices.
47pub struct VirtioMmioDevice {
48    device: Box<dyn VirtioDevice>,
49    device_activated: bool,
50
51    interrupt: Option<Interrupt>,
52    interrupt_evt: Option<IrqEdgeEvent>,
53    async_intr_status: bool,
54    queues: Vec<QueueConfig>,
55    queue_evts: Vec<Event>,
56    mem: GuestMemory,
57    device_feature_select: u32,
58    driver_feature_select: u32,
59    queue_select: u16,
60    driver_status: u8,
61    mmio_base: u64,
62    irq_num: u32,
63    config_generation: u32,
64}
65
66impl VirtioMmioDevice {
67    /// Constructs a new MMIO transport for the given virtio device.
68    pub fn new(
69        mem: GuestMemory,
70        device: Box<dyn VirtioDevice>,
71        async_intr_status: bool,
72    ) -> Result<Self> {
73        let mut queue_evts = Vec::new();
74        for _ in device.queue_max_sizes() {
75            queue_evts.push(Event::new()?)
76        }
77        let queues = device
78            .queue_max_sizes()
79            .iter()
80            .map(|&s| QueueConfig::new(s, device.features()))
81            .collect();
82
83        Ok(VirtioMmioDevice {
84            device,
85            device_activated: false,
86            interrupt: None,
87            interrupt_evt: None,
88            async_intr_status,
89            queues,
90            queue_evts,
91            mem,
92            device_feature_select: 0,
93            driver_feature_select: 0,
94            queue_select: 0,
95            driver_status: 0,
96            mmio_base: 0,
97            irq_num: 0,
98            config_generation: 0,
99        })
100    }
101    pub fn ioevents(&self) -> Vec<(&Event, u64, Datamatch)> {
102        self.queue_evts
103            .iter()
104            .enumerate()
105            .map(|(i, event)| {
106                (
107                    event,
108                    self.mmio_base + VIRTIO_MMIO_QUEUE_NOTIFY as u64,
109                    Datamatch::U32(Some(i.try_into().unwrap())),
110                )
111            })
112            .collect()
113    }
114
115    fn is_driver_ready(&self) -> bool {
116        let ready_bits = (VIRTIO_CONFIG_S_ACKNOWLEDGE
117            | VIRTIO_CONFIG_S_DRIVER
118            | VIRTIO_CONFIG_S_DRIVER_OK
119            | VIRTIO_CONFIG_S_FEATURES_OK) as u8;
120        self.driver_status == ready_bits && self.driver_status & VIRTIO_CONFIG_S_FAILED as u8 == 0
121    }
122
123    /// Determines if the driver has requested the device reset itself
124    fn is_reset_requested(&self) -> bool {
125        self.driver_status == DEVICE_RESET as u8
126    }
127
128    fn device_type(&self) -> u32 {
129        self.device.device_type() as u32
130    }
131
132    /// Activates the underlying `VirtioDevice`. `assign_irq` has to be called first.
133    fn activate(&mut self) -> anyhow::Result<()> {
134        let interrupt_evt = if let Some(ref evt) = self.interrupt_evt {
135            evt.try_clone()
136                .with_context(|| format!("{} failed to clone interrupt_evt", self.debug_label()))?
137        } else {
138            return Err(anyhow!("{} interrupt_evt is none", self.debug_label()));
139        };
140
141        let mem = self.mem.clone();
142        let interrupt = Interrupt::new_mmio(interrupt_evt, self.async_intr_status);
143        self.interrupt = Some(interrupt.clone());
144
145        // Use ready queues and their events.
146        let queues = self
147            .queues
148            .iter_mut()
149            .zip(self.queue_evts.iter())
150            .enumerate()
151            .filter(|(_, (q, _))| q.ready())
152            .map(|(queue_index, (queue, evt))| {
153                let queue_evt = evt.try_clone().context("failed to clone queue_evt")?;
154                Ok((
155                    queue_index,
156                    queue
157                        .activate(&mem, queue_evt, interrupt.clone())
158                        .context("failed to activate queue")?,
159                ))
160            })
161            .collect::<anyhow::Result<BTreeMap<usize, Queue>>>()?;
162
163        if let Err(e) = self.device.activate(mem, interrupt, queues) {
164            error!("{} activate failed: {:#}", self.debug_label(), e);
165            self.driver_status |= VIRTIO_CONFIG_S_NEEDS_RESET as u8;
166        } else {
167            self.device_activated = true;
168        }
169
170        Ok(())
171    }
172
173    fn read_mmio(&self, info: BusAccessInfo, data: &mut [u8]) {
174        if data.len() != std::mem::size_of::<u32>() {
175            warn!(
176                "{}: unsupported read length {}, only support 4 bytes read",
177                self.debug_label(),
178                data.len()
179            );
180            return;
181        }
182
183        if info.offset >= VIRTIO_MMIO_CONFIG as u64 {
184            self.device
185                .read_config(info.offset - VIRTIO_MMIO_CONFIG as u64, data);
186            return;
187        }
188
189        let val = match info.offset as u32 {
190            VIRTIO_MMIO_MAGIC_VALUE => VIRT_MAGIC,
191            VIRTIO_MMIO_VERSION => VIRT_VERSION.into(), // legacy is not supported
192            VIRTIO_MMIO_DEVICE_ID => self.device_type(),
193            VIRTIO_MMIO_VENDOR_ID => VIRT_VENDOR,
194            VIRTIO_MMIO_DEVICE_FEATURES => {
195                // Only 64 bits of features (2 pages) are defined for now, so limit
196                // device_feature_select to avoid shifting by 64 or more bits.
197                if self.device_feature_select < 2 {
198                    (self.device.features() >> (self.device_feature_select * 32)) as u32
199                } else {
200                    0
201                }
202            }
203            VIRTIO_MMIO_QUEUE_NUM_MAX => self.with_queue(|q| q.max_size()).unwrap_or(0).into(),
204            VIRTIO_MMIO_QUEUE_PFN => {
205                warn!(
206                    "{}: read from legacy register {}, in non-legacy mode",
207                    self.debug_label(),
208                    info.offset,
209                );
210                0
211            }
212            VIRTIO_MMIO_QUEUE_READY => self.with_queue(|q| q.ready()).unwrap_or(false).into(),
213            VIRTIO_MMIO_INTERRUPT_STATUS => {
214                if let Some(interrupt) = &self.interrupt {
215                    interrupt.read_interrupt_status().into()
216                } else {
217                    0
218                }
219            }
220            VIRTIO_MMIO_STATUS => self.driver_status.into(),
221            VIRTIO_MMIO_CONFIG_GENERATION => self.config_generation,
222            _ => {
223                warn!("{}: unsupported read address {}", self.debug_label(), info);
224                return;
225            }
226        };
227
228        let val_arr = val.to_le_bytes();
229        data.copy_from_slice(&val_arr);
230    }
231
232    fn write_mmio(&mut self, info: BusAccessInfo, data: &[u8]) {
233        if data.len() != std::mem::size_of::<u32>() {
234            warn!(
235                "{}: unsupported write length {}, only support 4 bytes write",
236                self.debug_label(),
237                data.len()
238            );
239            return;
240        }
241
242        if info.offset >= VIRTIO_MMIO_CONFIG as u64 {
243            self.device
244                .write_config(info.offset - VIRTIO_MMIO_CONFIG as u64, data);
245            return;
246        }
247
248        // This unwrap cannot fail since data.len() is checked.
249        let val = u32::from_le_bytes(data.try_into().unwrap());
250
251        macro_rules! hi {
252            ($q:expr, $get:ident, $set:ident, $x:expr) => {
253                $q.$set(($q.$get() & 0xffffffff) | (($x as u64) << 32))
254            };
255        }
256        macro_rules! lo {
257            ($q:expr, $get:ident, $set:ident, $x:expr) => {
258                $q.$set(($q.$get() & !0xffffffff) | ($x as u64))
259            };
260        }
261
262        match info.offset as u32 {
263            VIRTIO_MMIO_DEVICE_FEATURES_SEL => self.device_feature_select = val,
264            VIRTIO_MMIO_DRIVER_FEATURES_SEL => self.driver_feature_select = val,
265            VIRTIO_MMIO_DRIVER_FEATURES => {
266                // Only 64 bits of features (2 pages) are defined for now, so limit
267                // device_feature_select to avoid shifting by 64 or more bits.
268                if self.driver_feature_select < 2 {
269                    let features: u64 = (val as u64) << (self.driver_feature_select * 32);
270                    self.device.ack_features(features);
271                    for queue in self.queues.iter_mut() {
272                        queue.ack_features(features);
273                    }
274                } else {
275                    // The guest might try to write features outside of the first
276                    // 64-bit in a second 64-bit (validly), but these should be zero
277                    if val != 0 {
278                        warn!(
279                            "invalid ack_features (page {}, value 0x{:x})",
280                            self.driver_feature_select, val
281                        );
282                    }
283                }
284            }
285            VIRTIO_MMIO_GUEST_PAGE_SIZE => warn!(
286                "{}: write to legacy register {}, in non-legacy mode",
287                self.debug_label(),
288                info.offset,
289            ),
290            VIRTIO_MMIO_QUEUE_SEL => self.queue_select = val as u16,
291            VIRTIO_MMIO_QUEUE_NUM => self.with_queue_mut(|q| q.set_size(val as u16)),
292            VIRTIO_MMIO_QUEUE_ALIGN => warn!(
293                "{}: write to legacy register {}, in non-legacy mode",
294                self.debug_label(),
295                info.offset,
296            ),
297            VIRTIO_MMIO_QUEUE_PFN => warn!(
298                "{}: write to legacy register {}, in non-legacy mode",
299                self.debug_label(),
300                info.offset,
301            ),
302            VIRTIO_MMIO_QUEUE_READY => self.with_queue_mut(|q| q.set_ready(val == 1)),
303            VIRTIO_MMIO_QUEUE_NOTIFY => {} // Handled with ioevents.
304            VIRTIO_MMIO_INTERRUPT_ACK => {
305                if let Some(interrupt) = &self.interrupt {
306                    interrupt.clear_interrupt_status_bits(val as u8)
307                }
308            }
309            VIRTIO_MMIO_STATUS => self.driver_status = val as u8,
310            VIRTIO_MMIO_QUEUE_DESC_LOW => {
311                self.with_queue_mut(|q| lo!(q, desc_table, set_desc_table, val))
312            }
313            VIRTIO_MMIO_QUEUE_DESC_HIGH => {
314                self.with_queue_mut(|q| hi!(q, desc_table, set_desc_table, val))
315            }
316            VIRTIO_MMIO_QUEUE_AVAIL_LOW => {
317                self.with_queue_mut(|q| lo!(q, avail_ring, set_avail_ring, val))
318            }
319            VIRTIO_MMIO_QUEUE_AVAIL_HIGH => {
320                self.with_queue_mut(|q| hi!(q, avail_ring, set_avail_ring, val))
321            }
322            VIRTIO_MMIO_QUEUE_USED_LOW => {
323                self.with_queue_mut(|q| lo!(q, used_ring, set_used_ring, val))
324            }
325            VIRTIO_MMIO_QUEUE_USED_HIGH => {
326                self.with_queue_mut(|q| hi!(q, used_ring, set_used_ring, val))
327            }
328            _ => {
329                warn!("{}: unsupported write address {}", self.debug_label(), info);
330                return;
331            }
332        };
333
334        if !self.device_activated && self.is_driver_ready() {
335            if let Err(e) = self.activate() {
336                error!("failed to activate device: {:#}", e);
337            }
338        }
339
340        // Device has been reset by the driver
341        if self.device_activated && self.is_reset_requested() {
342            if let Err(e) = self.device.reset() {
343                error!("failed to reset {} device: {:#}", self.debug_label(), e);
344            } else {
345                self.device_activated = false;
346                // reset queues
347                self.queues.iter_mut().for_each(QueueConfig::reset);
348                // select queue 0 by default
349                self.queue_select = 0;
350                // reset interrupt
351                self.interrupt = None;
352            }
353        }
354    }
355
356    fn with_queue<U, F>(&self, f: F) -> Option<U>
357    where
358        F: FnOnce(&QueueConfig) -> U,
359    {
360        self.queues.get(self.queue_select as usize).map(f)
361    }
362
363    fn with_queue_mut<F>(&mut self, f: F)
364    where
365        F: FnOnce(&mut QueueConfig),
366    {
367        if let Some(queue) = self.queues.get_mut(self.queue_select as usize) {
368            f(queue);
369        }
370    }
371
372    pub fn allocate_regions(
373        &mut self,
374        resources: &mut SystemAllocator,
375    ) -> std::result::Result<Vec<(u64, u64)>, resources::Error> {
376        let mut ranges = Vec::new();
377        let alloc_id = resources.get_anon_alloc();
378        let start_addr = resources.allocate_mmio(
379            VIRTIO_MMIO_REGION_SZ,
380            alloc_id,
381            "virtio_mmio".to_string(),
382            AllocOptions::new().align(pagesize() as u64),
383        )?;
384        self.mmio_base = start_addr;
385        ranges.push((start_addr, VIRTIO_MMIO_REGION_SZ));
386        Ok(ranges)
387    }
388
389    pub fn assign_irq(&mut self, irq_evt: &IrqEdgeEvent, irq_num: u32) {
390        self.interrupt_evt = Some(irq_evt.try_clone().unwrap());
391        self.irq_num = irq_num;
392    }
393
394    pub fn keep_rds(&self) -> Vec<RawDescriptor> {
395        let mut rds = self.device.keep_rds();
396        if let Some(interrupt_evt) = &self.interrupt_evt {
397            rds.extend(interrupt_evt.as_raw_descriptors());
398        }
399        rds
400    }
401
402    fn on_device_sandboxed(&mut self) {
403        self.device.on_device_sandboxed();
404    }
405}
406
407impl Aml for VirtioMmioDevice {
408    fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
409        aml::Device::new(
410            "VIOM".into(),
411            vec![
412                &aml::Name::new("_HID".into(), &"LNRO0005"),
413                &aml::Name::new(
414                    "_CRS".into(),
415                    &aml::ResourceTemplate::new(vec![
416                        &aml::AddressSpace::new_memory(
417                            aml::AddressSpaceCachable::NotCacheable,
418                            true,
419                            self.mmio_base,
420                            self.mmio_base + VIRTIO_MMIO_REGION_SZ - 1,
421                        ),
422                        &aml::Interrupt::new(true, true, false, false, self.irq_num),
423                    ]),
424                ),
425            ],
426        )
427        .to_aml_bytes(bytes);
428    }
429}
430
431impl BusDeviceObj for VirtioMmioDevice {}
432
433impl BusDevice for VirtioMmioDevice {
434    fn debug_label(&self) -> String {
435        format!("mmio{}", self.device.debug_label())
436    }
437
438    fn device_id(&self) -> DeviceId {
439        PlatformDeviceId::VirtioMmio.into()
440    }
441
442    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
443        self.read_mmio(info, data)
444    }
445
446    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
447        self.write_mmio(info, data)
448    }
449
450    fn on_sandboxed(&mut self) {
451        self.on_device_sandboxed();
452    }
453}
454
455// TODO: Mimic the Suspendable impl in ViritoPciDevice when/if someone wants it.
456impl Suspendable for VirtioMmioDevice {}