Skip to main content

virt/
irqcon.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Types related to supporting an interrupt controller.
5
6pub use x86defs::apic::DeliveryMode;
7
8use hvdef::HvInterruptType;
9use inspect::Inspect;
10use parking_lot::Mutex;
11use std::fmt::Debug;
12use x86defs::msi::MsiAddress;
13use x86defs::msi::MsiData;
14
15/// Trait for an interrupt controller that can deliver MSIs from an IO-APIC.
16///
17/// This is used to map line interrupts, which may be level triggered. Some
18/// hypervisors (notably KVM) will not generate EOI exits for a
19/// level-triggered interrupt request unless the request has been registered
20/// as a route on one of the IO-APIC IRQs.
21///
22/// `irq` must be less than [`IRQ_LINES`] for both methods; callers (the
23/// IO-APIC device) guarantee this, and implementations may panic otherwise.
24pub trait IoApicRouting: Send + Sync {
25    /// Sets the associated interrupt request for the given irq.
26    fn set_irq_route(&self, irq: u8, request: Option<MsiRequest>);
27
28    /// Asserts the given irq, using the route established by `set_irq_route`.
29    fn assert_irq(&self, irq: u8);
30}
31
32/// Trait for controlling interrupt states on a GICv3 interrupt controller.
33pub trait ControlGic: Send + Sync {
34    /// Sets the assertion state of a GICv3 SPI.
35    fn set_spi_irq(&self, irq_id: u32, high: bool);
36}
37
38// The number of IRQ lines for the interrupt controller.
39pub const IRQ_LINES: usize = 24;
40
41/// A message-signaled interrupt request.
42#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect)]
43pub struct MsiRequest {
44    /// The MSI address.
45    #[inspect(hex)]
46    pub address: u64,
47    /// The data payload.
48    #[inspect(hex)]
49    pub data: u32,
50}
51
52impl MsiRequest {
53    /// Creates a new MSI request for an x86 system.
54    pub fn new_x86(
55        mode: DeliveryMode,
56        destination: u32,
57        is_logical_destination: bool,
58        vector: u8,
59        is_level_triggered: bool,
60    ) -> Self {
61        let address = MsiAddress::new()
62            .with_address(x86defs::msi::MSI_ADDRESS)
63            .with_redirection_hint(mode == DeliveryMode::LOWEST_PRIORITY)
64            .with_virt_destination(destination as u16)
65            .with_destination_mode_logical(is_logical_destination);
66
67        let data = MsiData::new()
68            .with_vector(vector)
69            .with_delivery_mode(mode.0 & 0x7)
70            .with_assert(is_level_triggered)
71            .with_trigger_mode_level(is_level_triggered);
72
73        Self {
74            address: u32::from(address).into(),
75            data: data.into(),
76        }
77    }
78
79    /// Interprets the MSI address and data as an x86 MSI request.
80    pub fn as_x86(&self) -> (MsiAddress, MsiData) {
81        (
82            MsiAddress::from(self.address as u32),
83            MsiData::from(self.data),
84        )
85    }
86
87    /// Constructs an interrupt control for sending this interrupt request to a
88    /// Microsoft hypervisor.
89    ///
90    /// Note that this may produce an invalid interrupt control that the
91    /// hypervisor will reject.
92    pub fn hv_x86_interrupt_control(&self) -> hvdef::HvInterruptControl {
93        let (address, data) = self.as_x86();
94        let ty = match DeliveryMode(data.delivery_mode()) {
95            DeliveryMode::FIXED => HvInterruptType::HvX64InterruptTypeFixed,
96            DeliveryMode::LOWEST_PRIORITY => HvInterruptType::HvX64InterruptTypeLowestPriority,
97            DeliveryMode::SMI => HvInterruptType::HvX64InterruptTypeSmi,
98            DeliveryMode::REMOTE_READ => HvInterruptType::HvX64InterruptTypeRemoteRead,
99            DeliveryMode::NMI => HvInterruptType::HvX64InterruptTypeNmi,
100            DeliveryMode::INIT => HvInterruptType::HvX64InterruptTypeInit,
101            DeliveryMode::SIPI => HvInterruptType::HvX64InterruptTypeSipi,
102            // Use an invalid interrupt type to force the hypervisor to reject
103            // this. Since other combinations of interrupt parameters are
104            // invalid and we are deferring that validation to the hypervisor,
105            // there is no reason to special case this one and add a failure
106            // path from this function.
107            _ => HvInterruptType(!0),
108        };
109        hvdef::HvInterruptControl::new()
110            .with_interrupt_type(ty)
111            .with_x86_level_triggered(data.trigger_mode_level())
112            .with_x86_logical_destination_mode(address.destination_mode_logical())
113    }
114}
115
116/// A set of IRQ routes.
117///
118/// This is used to implement [`IoApicRouting`] when the backing hypervisor does
119/// not require such routes internally.
120#[derive(Debug, Inspect)]
121pub struct IrqRoutes {
122    #[inspect(
123        with = "|x| inspect::adhoc(|req| inspect::iter_by_index(x.lock().iter()).inspect(req))"
124    )]
125    routes: Mutex<Vec<Option<MsiRequest>>>,
126}
127
128impl Default for IrqRoutes {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl IrqRoutes {
135    pub fn new() -> Self {
136        let routes = vec![None; IRQ_LINES];
137        Self {
138            routes: Mutex::new(routes),
139        }
140    }
141
142    /// Sets the associated interrupt request for the given irq.
143    pub fn set_irq_route(&self, irq: u8, request: Option<MsiRequest>) {
144        self.routes.lock()[irq as usize] = request;
145    }
146
147    /// Asserts the given irq, using the route established by `set_irq_route`.
148    ///
149    /// Calls `assert` to deliver the interrupt.
150    pub fn assert_irq(&self, irq: u8, assert: impl FnOnce(MsiRequest)) {
151        let request = self.routes.lock()[irq as usize];
152        match request {
153            Some(request) => {
154                assert(request);
155            }
156            None => {
157                tracelimit::warn_ratelimited!(irq, "irq for masked interrupt");
158            }
159        }
160    }
161}