• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 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 use std::sync::atomic::AtomicUsize;
6 use std::sync::atomic::Ordering;
7 use std::sync::Arc;
8 
9 #[cfg(target_arch = "x86_64")]
10 use base::error;
11 use base::Event;
12 use serde::Deserialize;
13 use serde::Serialize;
14 use sync::Mutex;
15 
16 use super::INTERRUPT_STATUS_CONFIG_CHANGED;
17 use super::INTERRUPT_STATUS_USED_RING;
18 use super::VIRTIO_MSI_NO_VECTOR;
19 #[cfg(target_arch = "x86_64")]
20 use crate::acpi::PmWakeupEvent;
21 use crate::irq_event::IrqEdgeEvent;
22 use crate::irq_event::IrqLevelEvent;
23 use crate::pci::MsixConfig;
24 
25 struct TransportPci {
26     irq_evt_lvl: IrqLevelEvent,
27     msix_config: Option<Arc<Mutex<MsixConfig>>>,
28     config_msix_vector: u16,
29 }
30 
31 enum Transport {
32     Pci {
33         pci: TransportPci,
34     },
35     Mmio {
36         irq_evt_edge: IrqEdgeEvent,
37     },
38     VhostUser {
39         call_evt: Event,
40         signal_config_changed_fn: Box<dyn Fn() + Send + Sync>,
41     },
42 }
43 
44 struct InterruptInner {
45     interrupt_status: AtomicUsize,
46     transport: Transport,
47     async_intr_status: bool,
48     #[cfg(target_arch = "x86_64")]
49     wakeup_event: Option<PmWakeupEvent>,
50 }
51 
52 impl InterruptInner {
53     /// Add `interrupt_status_mask` to any existing interrupt status.
54     ///
55     /// Returns `true` if the interrupt should be triggered after this update.
update_interrupt_status(&self, interrupt_status_mask: u32) -> bool56     fn update_interrupt_status(&self, interrupt_status_mask: u32) -> bool {
57         // Set bit in ISR and inject the interrupt if it was not already pending.
58         // Don't need to inject the interrupt if the guest hasn't processed it.
59         // In hypervisors where interrupt_status is updated asynchronously, inject the
60         // interrupt even if the previous interrupt appears to be already pending.
61         self.interrupt_status
62             .fetch_or(interrupt_status_mask as usize, Ordering::SeqCst)
63             == 0
64             || self.async_intr_status
65     }
66 }
67 
68 #[derive(Clone)]
69 pub struct Interrupt {
70     inner: Arc<InterruptInner>,
71 }
72 
73 #[derive(Serialize, Deserialize)]
74 pub struct InterruptSnapshot {
75     interrupt_status: usize,
76 }
77 
78 impl Interrupt {
79     /// Writes to the irqfd to VMM to deliver virtual interrupt to the guest.
80     ///
81     /// If MSI-X is enabled in this device, MSI-X interrupt is preferred.
82     /// Write to the irqfd to VMM to deliver virtual interrupt to the guest
signal(&self, vector: u16, interrupt_status_mask: u32)83     pub fn signal(&self, vector: u16, interrupt_status_mask: u32) {
84         #[cfg(target_arch = "x86_64")]
85         if let Some(wakeup_event) = self.inner.wakeup_event.as_ref() {
86             if let Err(e) = wakeup_event.trigger_wakeup() {
87                 error!("Wakeup trigger failed {:?}", e);
88             }
89         }
90         match &self.inner.transport {
91             Transport::Pci { pci } => {
92                 // Don't need to set ISR for MSI-X interrupts
93                 if let Some(msix_config) = &pci.msix_config {
94                     let mut msix_config = msix_config.lock();
95                     if msix_config.enabled() {
96                         if vector != VIRTIO_MSI_NO_VECTOR {
97                             msix_config.trigger(vector);
98                         }
99                         return;
100                     }
101                 }
102 
103                 if self.inner.update_interrupt_status(interrupt_status_mask) {
104                     pci.irq_evt_lvl.trigger().unwrap();
105                 }
106             }
107             Transport::Mmio { irq_evt_edge } => {
108                 if self.inner.update_interrupt_status(interrupt_status_mask) {
109                     irq_evt_edge.trigger().unwrap();
110                 }
111             }
112             Transport::VhostUser { call_evt, .. } => {
113                 // TODO(b/187487351): To avoid sending unnecessary events, we might want to support
114                 // interrupt status. For this purpose, we need a mechanism to share interrupt status
115                 // between the vmm and the device process.
116                 call_evt.signal().unwrap();
117             }
118         }
119     }
120 
121     /// Notify the driver that buffers have been placed in the used queue.
signal_used_queue(&self, vector: u16)122     pub fn signal_used_queue(&self, vector: u16) {
123         self.signal(vector, INTERRUPT_STATUS_USED_RING)
124     }
125 
126     /// Notify the driver that the device configuration has changed.
signal_config_changed(&self)127     pub fn signal_config_changed(&self) {
128         match &self.inner.as_ref().transport {
129             Transport::Pci { pci } => {
130                 self.signal(pci.config_msix_vector, INTERRUPT_STATUS_CONFIG_CHANGED)
131             }
132             Transport::Mmio { .. } => {
133                 self.signal(VIRTIO_MSI_NO_VECTOR, INTERRUPT_STATUS_CONFIG_CHANGED)
134             }
135             Transport::VhostUser {
136                 signal_config_changed_fn,
137                 ..
138             } => signal_config_changed_fn(),
139         }
140     }
141 
142     /// Get the event to signal resampling is needed if it exists.
get_resample_evt(&self) -> Option<&Event>143     pub fn get_resample_evt(&self) -> Option<&Event> {
144         match &self.inner.as_ref().transport {
145             Transport::Pci { pci } => Some(pci.irq_evt_lvl.get_resample()),
146             _ => None,
147         }
148     }
149 
150     /// Reads the status and writes to the interrupt event. Doesn't read the resample event, it
151     /// assumes the resample has been requested.
do_interrupt_resample(&self)152     pub fn do_interrupt_resample(&self) {
153         if self.inner.interrupt_status.load(Ordering::SeqCst) != 0 {
154             match &self.inner.as_ref().transport {
155                 Transport::Pci { pci } => pci.irq_evt_lvl.trigger().unwrap(),
156                 _ => panic!("do_interrupt_resample() not supported"),
157             }
158         }
159     }
160 }
161 
162 impl Interrupt {
new( irq_evt_lvl: IrqLevelEvent, msix_config: Option<Arc<Mutex<MsixConfig>>>, config_msix_vector: u16, #[cfg(target_arch = "x86_64")] wakeup_event: Option<PmWakeupEvent>, ) -> Interrupt163     pub fn new(
164         irq_evt_lvl: IrqLevelEvent,
165         msix_config: Option<Arc<Mutex<MsixConfig>>>,
166         config_msix_vector: u16,
167         #[cfg(target_arch = "x86_64")] wakeup_event: Option<PmWakeupEvent>,
168     ) -> Interrupt {
169         Interrupt {
170             inner: Arc::new(InterruptInner {
171                 interrupt_status: AtomicUsize::new(0),
172                 async_intr_status: false,
173                 transport: Transport::Pci {
174                     pci: TransportPci {
175                         irq_evt_lvl,
176                         msix_config,
177                         config_msix_vector,
178                     },
179                 },
180                 #[cfg(target_arch = "x86_64")]
181                 wakeup_event,
182             }),
183         }
184     }
185 
186     /// Create a new `Interrupt`, restoring internal state to match `snapshot`.
187     ///
188     /// The other arguments are assumed to be snapshot'd and restore'd elsewhere.
new_from_snapshot( irq_evt_lvl: IrqLevelEvent, msix_config: Option<Arc<Mutex<MsixConfig>>>, config_msix_vector: u16, snapshot: InterruptSnapshot, #[cfg(target_arch = "x86_64")] wakeup_event: Option<PmWakeupEvent>, ) -> Interrupt189     pub fn new_from_snapshot(
190         irq_evt_lvl: IrqLevelEvent,
191         msix_config: Option<Arc<Mutex<MsixConfig>>>,
192         config_msix_vector: u16,
193         snapshot: InterruptSnapshot,
194         #[cfg(target_arch = "x86_64")] wakeup_event: Option<PmWakeupEvent>,
195     ) -> Interrupt {
196         Interrupt {
197             inner: Arc::new(InterruptInner {
198                 interrupt_status: AtomicUsize::new(snapshot.interrupt_status),
199                 async_intr_status: false,
200                 transport: Transport::Pci {
201                     pci: TransportPci {
202                         irq_evt_lvl,
203                         msix_config,
204                         config_msix_vector,
205                     },
206                 },
207                 #[cfg(target_arch = "x86_64")]
208                 wakeup_event,
209             }),
210         }
211     }
212 
new_mmio(irq_evt_edge: IrqEdgeEvent, async_intr_status: bool) -> Interrupt213     pub fn new_mmio(irq_evt_edge: IrqEdgeEvent, async_intr_status: bool) -> Interrupt {
214         Interrupt {
215             inner: Arc::new(InterruptInner {
216                 interrupt_status: AtomicUsize::new(0),
217                 transport: Transport::Mmio { irq_evt_edge },
218                 async_intr_status,
219                 #[cfg(target_arch = "x86_64")]
220                 wakeup_event: None,
221             }),
222         }
223     }
224 
225     /// Create an `Interrupt` wrapping a vhost-user vring call event and function that sends a
226     /// VHOST_USER_BACKEND_CONFIG_CHANGE_MSG to the frontend.
new_vhost_user( call_evt: Event, signal_config_changed_fn: Box<dyn Fn() + Send + Sync>, ) -> Interrupt227     pub fn new_vhost_user(
228         call_evt: Event,
229         signal_config_changed_fn: Box<dyn Fn() + Send + Sync>,
230     ) -> Interrupt {
231         Interrupt {
232             inner: Arc::new(InterruptInner {
233                 interrupt_status: AtomicUsize::new(0),
234                 transport: Transport::VhostUser {
235                     call_evt,
236                     signal_config_changed_fn,
237                 },
238                 async_intr_status: false,
239                 #[cfg(target_arch = "x86_64")]
240                 wakeup_event: None,
241             }),
242         }
243     }
244 
245     #[cfg(test)]
new_for_test() -> Interrupt246     pub fn new_for_test() -> Interrupt {
247         Interrupt::new(
248             IrqLevelEvent::new().unwrap(),
249             None,
250             VIRTIO_MSI_NO_VECTOR,
251             #[cfg(target_arch = "x86_64")]
252             None,
253         )
254     }
255 
256     #[cfg(test)]
new_for_test_with_msix() -> Interrupt257     pub fn new_for_test_with_msix() -> Interrupt {
258         let (_, unused_config_tube) = base::Tube::pair().unwrap();
259         let msix_vectors = 2;
260         let msix_cfg = MsixConfig::new(
261             msix_vectors,
262             unused_config_tube,
263             0,
264             "test_device".to_owned(),
265         );
266 
267         Interrupt::new(
268             IrqLevelEvent::new().unwrap(),
269             Some(Arc::new(Mutex::new(msix_cfg))),
270             msix_vectors,
271             #[cfg(target_arch = "x86_64")]
272             None,
273         )
274     }
275 
276     /// Get a reference to the interrupt event.
get_interrupt_evt(&self) -> &Event277     pub fn get_interrupt_evt(&self) -> &Event {
278         match &self.inner.as_ref().transport {
279             Transport::Pci { pci } => pci.irq_evt_lvl.get_trigger(),
280             Transport::Mmio { irq_evt_edge } => irq_evt_edge.get_trigger(),
281             Transport::VhostUser { call_evt, .. } => call_evt,
282         }
283     }
284 
285     /// Handle interrupt resampling event, reading the value from the event and doing the resample.
interrupt_resample(&self)286     pub fn interrupt_resample(&self) {
287         match &self.inner.as_ref().transport {
288             Transport::Pci { pci } => {
289                 pci.irq_evt_lvl.clear_resample();
290                 self.do_interrupt_resample();
291             }
292             _ => panic!("interrupt_resample() not supported"),
293         }
294     }
295 
296     /// Get a reference to the msix configuration
get_msix_config(&self) -> &Option<Arc<Mutex<MsixConfig>>>297     pub fn get_msix_config(&self) -> &Option<Arc<Mutex<MsixConfig>>> {
298         match &self.inner.as_ref().transport {
299             Transport::Pci { pci } => &pci.msix_config,
300             _ => &None,
301         }
302     }
303 
304     /// Reads the current value of the interrupt status.
read_interrupt_status(&self) -> u8305     pub fn read_interrupt_status(&self) -> u8 {
306         self.inner.interrupt_status.load(Ordering::SeqCst) as u8
307     }
308 
309     /// Reads the current value of the interrupt status and resets it to 0.
read_and_reset_interrupt_status(&self) -> u8310     pub fn read_and_reset_interrupt_status(&self) -> u8 {
311         self.inner.interrupt_status.swap(0, Ordering::SeqCst) as u8
312     }
313 
314     /// Clear the bits set in `mask` in the interrupt status.
clear_interrupt_status_bits(&self, mask: u8)315     pub fn clear_interrupt_status_bits(&self, mask: u8) {
316         self.inner
317             .interrupt_status
318             .fetch_and(!(mask as usize), Ordering::SeqCst);
319     }
320 
321     /// Snapshot internal state. Can be restored with with `Interrupt::new_from_snapshot`.
snapshot(&self) -> InterruptSnapshot322     pub fn snapshot(&self) -> InterruptSnapshot {
323         InterruptSnapshot {
324             interrupt_status: self.inner.interrupt_status.load(Ordering::SeqCst),
325         }
326     }
327 
328     #[cfg(target_arch = "x86_64")]
set_wakeup_event_active(&self, active: bool)329     pub fn set_wakeup_event_active(&self, active: bool) {
330         if let Some(wakeup_event) = self.inner.wakeup_event.as_ref() {
331             wakeup_event.set_active(active);
332         }
333     }
334 }
335