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