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