vmotherboard/chipset/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Notable Exports: [`Chipset`], [`ChipsetBuilder`]
5
6pub mod backing;
7mod builder;
8mod io_ranges;
9mod line_sets;
10
11pub use self::builder::ChipsetBuilder;
12pub use self::builder::ChipsetDevices;
13pub use self::builder::DynamicDeviceUnit;
14
15use self::io_ranges::IoRanges;
16use self::io_ranges::LookupResult;
17use crate::DebugEventHandler;
18use chipset_device::ChipsetDevice;
19use chipset_device::io::IoError;
20use chipset_device::io::IoResult;
21use closeable_mutex::CloseableMutex;
22use cvm_tracing::CVM_CONFIDENTIAL;
23use inspect::Inspect;
24use std::future::poll_fn;
25use std::sync::Arc;
26
27/// The "glue" that interconnects virtual devices, and exposes an API for
28/// external entities (such as VCPUs) to access devices.
29#[derive(Inspect)]
30pub struct Chipset {
31    #[inspect(rename = "mmio")]
32    mmio_ranges: IoRanges<u64>,
33    #[inspect(rename = "pio")]
34    pio_ranges: IoRanges<u16>,
35
36    #[inspect(rename = "has_pic", with = "Option::is_some")]
37    pic: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
38    #[inspect(rename = "has_eoi_handler", with = "Option::is_some")]
39    eoi_handler: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
40
41    #[inspect(skip)]
42    debug_event_handler: Arc<dyn DebugEventHandler>,
43}
44
45enum IoType<'a> {
46    Read(&'a mut [u8]),
47    Write(&'a [u8]),
48}
49
50#[derive(Copy, Clone, Debug)]
51enum IoKind {
52    Pio,
53    Mmio,
54}
55
56impl IoType<'_> {
57    fn bytes(&self) -> &[u8] {
58        match self {
59            IoType::Read(b) => b,
60            IoType::Write(b) => b,
61        }
62    }
63}
64
65impl Chipset {
66    async fn handle_io_result(
67        &self,
68        lookup: LookupResult,
69        vp: u32,
70        kind: IoKind,
71        address: u64,
72        len: usize,
73        mut io_type: IoType<'_>,
74        result: IoResult,
75    ) {
76        if lookup.debug_break {
77            tracing::warn!(
78                device = &*lookup.dev_name,
79                address,
80                len,
81                ?kind,
82                "debug break due to io"
83            );
84            self.debug_event_handler.on_debug_break(Some(vp));
85        }
86        match result {
87            IoResult::Ok => {}
88            IoResult::Defer(mut token) => match &mut io_type {
89                IoType::Read(bytes) => {
90                    if let Err(err) = poll_fn(|cx| token.poll_read(cx, bytes)).await {
91                        self.handle_io_error(err, &lookup, kind, address, len, &mut io_type);
92                    }
93                }
94                IoType::Write(_) => {
95                    if let Err(err) = poll_fn(|cx| token.poll_write(cx)).await {
96                        self.handle_io_error(err, &lookup, kind, address, len, &mut io_type);
97                    }
98                }
99            },
100            IoResult::Err(err) => {
101                self.handle_io_error(err, &lookup, kind, address, len, &mut io_type);
102            }
103        };
104
105        if let Some(range_name) = &lookup.trace {
106            // Don't lower the tracing level or the whole thing is
107            // useless.
108            tracing::info!(
109                device = &*lookup.dev_name,
110                range_name = range_name.as_ref(),
111                ?kind,
112                address,
113                direction = if matches!(io_type, IoType::Read(_)) {
114                    "read"
115                } else {
116                    "write"
117                },
118                data = format_args!("{:02x?}", io_type.bytes()),
119                "device io"
120            );
121        }
122    }
123
124    fn handle_io_error(
125        &self,
126        err: IoError,
127        lookup: &LookupResult,
128        kind: IoKind,
129        address: u64,
130        len: usize,
131        io_type: &mut IoType<'_>,
132    ) {
133        let error = match err {
134            IoError::InvalidRegister => "register not present",
135            IoError::InvalidAccessSize => "invalid access size",
136            IoError::UnalignedAccess => "unaligned access",
137            IoError::NoResponse => "device didn't respond",
138        };
139        match io_type {
140            IoType::Read(bytes) => {
141                // Fill data with !0 to indicate an error to the guest.
142                bytes.fill(!0);
143                tracelimit::warn_ratelimited!(
144                    CVM_CONFIDENTIAL,
145                    device = &*lookup.dev_name,
146                    address,
147                    len,
148                    ?kind,
149                    error,
150                    "device io read error"
151                );
152            }
153            IoType::Write(bytes) => tracelimit::warn_ratelimited!(
154                CVM_CONFIDENTIAL,
155                device = &*lookup.dev_name,
156                address,
157                len,
158                ?kind,
159                error,
160                ?bytes,
161                "device io write error"
162            ),
163        }
164    }
165
166    /// Dispatch a MMIO read to the given address.
167    pub async fn mmio_read(&self, vp: u32, address: u64, data: &mut [u8]) {
168        let lookup = self.mmio_ranges.lookup(address, true);
169        let r = lookup
170            .dev
171            .lock()
172            .supports_mmio()
173            .expect("objects on the mmio bus support mmio")
174            .mmio_read(address, data);
175
176        self.handle_io_result(
177            lookup,
178            vp,
179            IoKind::Mmio,
180            address,
181            data.len(),
182            IoType::Read(data),
183            r,
184        )
185        .await
186    }
187
188    /// Dispatch a MMIO write to the given address.
189    pub async fn mmio_write(&self, vp: u32, address: u64, data: &[u8]) {
190        let lookup = self.mmio_ranges.lookup(address, false);
191        let r = lookup
192            .dev
193            .lock()
194            .supports_mmio()
195            .expect("objects on the mmio bus support mmio")
196            .mmio_write(address, data);
197
198        self.handle_io_result(
199            lookup,
200            vp,
201            IoKind::Mmio,
202            address,
203            data.len(),
204            IoType::Write(data),
205            r,
206        )
207        .await
208    }
209
210    /// Check if a MMIO device exists at the given address
211    pub fn is_mmio(&self, addr: u64) -> bool {
212        self.mmio_ranges.is_occupied(addr)
213    }
214
215    /// Dispatch a Port IO read to the given address.
216    pub async fn io_read(&self, vp: u32, port: u16, data: &mut [u8]) {
217        let lookup = self.pio_ranges.lookup(port, true);
218        let r = lookup
219            .dev
220            .lock()
221            .supports_pio()
222            .expect("objects on the pio bus support pio")
223            .io_read(port, data);
224
225        self.handle_io_result(
226            lookup,
227            vp,
228            IoKind::Pio,
229            port.into(),
230            data.len(),
231            IoType::Read(data),
232            r,
233        )
234        .await
235    }
236
237    /// Dispatch a Port IO write to the given address.
238    pub async fn io_write(&self, vp: u32, port: u16, data: &[u8]) {
239        let lookup = self.pio_ranges.lookup(port, false);
240        let r = lookup
241            .dev
242            .lock()
243            .supports_pio()
244            .expect("objects on the pio bus support pio")
245            .io_write(port, data);
246
247        self.handle_io_result(
248            lookup,
249            vp,
250            IoKind::Pio,
251            port.into(),
252            data.len(),
253            IoType::Write(data),
254            r,
255        )
256        .await
257    }
258
259    /// Gets the vector of the next interrupt to inject from the legacy
260    /// interrupt controller (PIC) and sets the IRQ in service.
261    pub fn acknowledge_pic_interrupt(&self) -> Option<u8> {
262        self.pic
263            .as_ref()?
264            .lock()
265            .supports_acknowledge_pic_interrupt()
266            .unwrap()
267            .acknowledge_interrupt()
268    }
269
270    /// Handle End Of Interrupt (EOI)
271    ///
272    /// A `u32` is used for the IRQ value for (future) ARM compat.
273    pub fn handle_eoi(&self, irq: u32) {
274        if let Some(eoi_handler) = &self.eoi_handler {
275            eoi_handler
276                .lock()
277                .supports_handle_eoi()
278                .unwrap()
279                .handle_eoi(irq);
280        } else {
281            unreachable!("eoi exit received without a registered interrupt controller");
282        }
283    }
284}
285
286#[derive(Debug)]
287pub enum PciConflictReason {
288    ExistingDev(Arc<str>),
289    MissingBus,
290}
291
292#[derive(Debug)]
293pub struct PciConflict {
294    pub bdf: (u8, u8, u8),
295    pub conflict_dev: Arc<str>,
296    pub reason: PciConflictReason,
297}
298
299impl std::fmt::Display for PciConflict {
300    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301        match &self.reason {
302            PciConflictReason::ExistingDev(existing_dev) => {
303                let (b, d, f) = self.bdf;
304                write!(
305                    fmt,
306                    "cannot attach {} to {:02x}:{:02x}:{}, already occupied by {}",
307                    self.conflict_dev, b, d, f, existing_dev
308                )
309            }
310            PciConflictReason::MissingBus => {
311                let (b, d, f) = self.bdf;
312                write!(
313                    fmt,
314                    "cannot attach {} to {:02x}:{:02x}:{}, no valid PCI bus",
315                    self.conflict_dev, b, d, f
316                )
317            }
318        }
319    }
320}
321
322#[derive(Debug)]
323pub enum PcieConflictReason {
324    ExistingDev(Arc<str>),
325    MissingDownstreamPort,
326    MissingEnumerator,
327}
328
329#[derive(Debug)]
330pub struct PcieConflict {
331    pub conflict_dev: Arc<str>,
332    pub reason: PcieConflictReason,
333}
334
335impl std::fmt::Display for PcieConflict {
336    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337        match &self.reason {
338            PcieConflictReason::ExistingDev(existing_dev) => {
339                write!(
340                    fmt,
341                    "cannot attach {}, port already occupied by {}",
342                    self.conflict_dev, existing_dev
343                )
344            }
345            PcieConflictReason::MissingDownstreamPort => {
346                write!(
347                    fmt,
348                    "cannot attach {}, no valid pcie downstream port",
349                    self.conflict_dev
350                )
351            }
352            PcieConflictReason::MissingEnumerator => {
353                write!(
354                    fmt,
355                    "cannot attach {}, no valid pcie enumerator",
356                    self.conflict_dev
357                )
358            }
359        }
360    }
361}