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(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        let r = match result {
87            IoResult::Ok => Ok(()),
88            IoResult::Defer(mut token) => match &mut io_type {
89                IoType::Read(bytes) => {
90                    let r = poll_fn(|cx| token.poll_read(cx, bytes)).await;
91                    if r.is_err() {
92                        bytes.fill(!0);
93                    }
94                    r
95                }
96                IoType::Write(_) => poll_fn(|cx| token.poll_write(cx)).await,
97            },
98            IoResult::Err(err) => {
99                let error = match err {
100                    IoError::InvalidRegister => "register not present",
101                    IoError::InvalidAccessSize => "invalid access size",
102                    IoError::UnalignedAccess => "unaligned access",
103                };
104                match &mut io_type {
105                    IoType::Read(bytes) => {
106                        // Fill data with !0 to indicate an error to the guest.
107                        bytes.fill(!0);
108                        tracelimit::warn_ratelimited!(
109                            CVM_CONFIDENTIAL,
110                            device = &*lookup.dev_name,
111                            address,
112                            len,
113                            ?kind,
114                            error,
115                            "device io read error"
116                        );
117                    }
118                    IoType::Write(bytes) => tracelimit::warn_ratelimited!(
119                        CVM_CONFIDENTIAL,
120                        device = &*lookup.dev_name,
121                        address,
122                        len,
123                        ?kind,
124                        error,
125                        ?bytes,
126                        "device io write error"
127                    ),
128                }
129                Ok(())
130            }
131        };
132
133        match r {
134            Ok(()) => {
135                if let Some(range_name) = &lookup.trace {
136                    // Don't lower the tracing level or the whole thing is
137                    // useless.
138                    tracing::info!(
139                        device = &*lookup.dev_name,
140                        range_name = range_name.as_ref(),
141                        ?kind,
142                        address,
143                        direction = if matches!(io_type, IoType::Read(_)) {
144                            "read"
145                        } else {
146                            "write"
147                        },
148                        data = format_args!("{:02x?}", io_type.bytes()),
149                        "device io"
150                    );
151                }
152            }
153            Err(err) => {
154                tracelimit::error_ratelimited!(
155                    CVM_CONFIDENTIAL,
156                    device = &*lookup.dev_name,
157                    ?kind,
158                    address,
159                    direction = if matches!(io_type, IoType::Read(_)) {
160                        "read"
161                    } else {
162                        "write"
163                    },
164                    error = &err as &dyn std::error::Error,
165                    "deferred io failed"
166                );
167            }
168        }
169    }
170
171    /// Dispatch a MMIO read to the given address.
172    pub async fn mmio_read(&self, vp: u32, address: u64, data: &mut [u8]) {
173        let lookup = self.mmio_ranges.lookup(address, true);
174        let r = lookup
175            .dev
176            .lock()
177            .supports_mmio()
178            .expect("objects on the mmio bus support mmio")
179            .mmio_read(address, data);
180
181        self.handle_io_result(
182            lookup,
183            vp,
184            IoKind::Mmio,
185            address,
186            data.len(),
187            IoType::Read(data),
188            r,
189        )
190        .await
191    }
192
193    /// Dispatch a MMIO write to the given address.
194    pub async fn mmio_write(&self, vp: u32, address: u64, data: &[u8]) {
195        let lookup = self.mmio_ranges.lookup(address, false);
196        let r = lookup
197            .dev
198            .lock()
199            .supports_mmio()
200            .expect("objects on the mmio bus support mmio")
201            .mmio_write(address, data);
202
203        self.handle_io_result(
204            lookup,
205            vp,
206            IoKind::Mmio,
207            address,
208            data.len(),
209            IoType::Write(data),
210            r,
211        )
212        .await
213    }
214
215    /// Check if a MMIO device exists at the given address
216    pub fn is_mmio(&self, addr: u64) -> bool {
217        self.mmio_ranges.is_occupied(addr)
218    }
219
220    /// Dispatch a Port IO read to the given address.
221    pub async fn io_read(&self, vp: u32, port: u16, data: &mut [u8]) {
222        let lookup = self.pio_ranges.lookup(port, true);
223        let r = lookup
224            .dev
225            .lock()
226            .supports_pio()
227            .expect("objects on the pio bus support pio")
228            .io_read(port, data);
229
230        self.handle_io_result(
231            lookup,
232            vp,
233            IoKind::Pio,
234            port.into(),
235            data.len(),
236            IoType::Read(data),
237            r,
238        )
239        .await
240    }
241
242    /// Dispatch a Port IO write to the given address.
243    pub async fn io_write(&self, vp: u32, port: u16, data: &[u8]) {
244        let lookup = self.pio_ranges.lookup(port, false);
245        let r = lookup
246            .dev
247            .lock()
248            .supports_pio()
249            .expect("objects on the pio bus support pio")
250            .io_write(port, data);
251
252        self.handle_io_result(
253            lookup,
254            vp,
255            IoKind::Pio,
256            port.into(),
257            data.len(),
258            IoType::Write(data),
259            r,
260        )
261        .await
262    }
263
264    /// Gets the vector of the next interrupt to inject from the legacy
265    /// interrupt controller (PIC) and sets the IRQ in service.
266    pub fn acknowledge_pic_interrupt(&self) -> Option<u8> {
267        self.pic
268            .as_ref()?
269            .lock()
270            .supports_acknowledge_pic_interrupt()
271            .unwrap()
272            .acknowledge_interrupt()
273    }
274
275    /// Handle End Of Interrupt (EOI)
276    ///
277    /// A `u32` is used for the IRQ value for (future) ARM compat.
278    pub fn handle_eoi(&self, irq: u32) {
279        if let Some(eoi_handler) = &self.eoi_handler {
280            eoi_handler
281                .lock()
282                .supports_handle_eoi()
283                .unwrap()
284                .handle_eoi(irq);
285        } else {
286            unreachable!("eoi exit received without a registered interrupt controller");
287        }
288    }
289}
290
291#[derive(Debug)]
292pub enum PciConflictReason {
293    ExistingDev(Arc<str>),
294    MissingBus,
295}
296
297#[derive(Debug)]
298pub struct PciConflict {
299    pub bdf: (u8, u8, u8),
300    pub conflict_dev: Arc<str>,
301    pub reason: PciConflictReason,
302}
303
304impl std::fmt::Display for PciConflict {
305    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        match &self.reason {
307            PciConflictReason::ExistingDev(existing_dev) => {
308                let (b, d, f) = self.bdf;
309                write!(
310                    fmt,
311                    "cannot attach {} to {:02x}:{:02x}:{}, already occupied by {}",
312                    self.conflict_dev, b, d, f, existing_dev
313                )
314            }
315            PciConflictReason::MissingBus => {
316                let (b, d, f) = self.bdf;
317                write!(
318                    fmt,
319                    "cannot attach {} to {:02x}:{:02x}:{}, no valid PCI bus",
320                    self.conflict_dev, b, d, f
321                )
322            }
323        }
324    }
325}