Skip to main content

chipset_device/
pci.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PCI configuration space access
5
6use crate::ChipsetDevice;
7use crate::io::IoResult;
8
9/// Implemented by devices which have a PCI config space.
10pub trait PciConfigSpace: ChipsetDevice {
11    /// Dispatch a PCI config space read to the device with the given address.
12    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult;
13    /// Dispatch a PCI config space write to the device with the given address.
14    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult;
15
16    /// Handle a PCI configuration space read with full routing context.
17    ///
18    /// This method receives configuration space accesses with the target bus
19    /// and function number. The interpretation of `function` depends on the
20    /// bus topology: on a legacy PCI bus it carries packed device/function
21    /// bits (0..=255), while downstream of a PCIe port the device number is
22    /// always zero so all 8 bits represent functions within a single
23    /// endpoint.
24    ///
25    /// A device can distinguish Type 0 (local) from Type 1 (forwarded)
26    /// configuration cycles by comparing `target_bus` and `secondary_bus`:
27    /// when they are equal the access targets this device directly (Type 0),
28    /// otherwise it should be routed downstream (Type 1). An SR-IOV
29    /// capable device can use `secondary_bus` together with `target_bus` and
30    /// `function` to compute the VF number.
31    ///
32    /// The default implementation dispatches function 0 to
33    /// [`pci_cfg_read`](Self::pci_cfg_read) and returns all-1s for other
34    /// functions (the standard "no device present" response). Routing
35    /// components (switches, bridges) and multi-function devices should
36    /// override this method.
37    ///
38    /// # Parameters
39    /// - `secondary_bus`: The secondary bus number of the downstream port
40    ///   that forwarded this access
41    /// - `target_bus`: The bus number targeted by the configuration access
42    /// - `function`: Device/function identifier — packed device/function on
43    ///   a legacy bus, or flat function number on PCIe
44    /// - `offset`: Configuration space offset
45    /// - `value`: Pointer to receive the read value
46    fn pci_cfg_read_with_routing(
47        &mut self,
48        secondary_bus: u8,
49        target_bus: u8,
50        function: u8,
51        offset: u16,
52        value: &mut u32,
53    ) -> IoResult {
54        if secondary_bus == target_bus && function == 0 {
55            self.pci_cfg_read(offset, value)
56        } else {
57            *value = !0;
58            IoResult::Ok
59        }
60    }
61
62    /// Handle a PCI configuration space write with full routing context.
63    ///
64    /// This method receives configuration space accesses with the target bus
65    /// and function number. The interpretation of `function` depends on the
66    /// bus topology: on a legacy PCI bus it carries packed device/function
67    /// bits (0..=255), while downstream of a PCIe port the device number is
68    /// always zero so all 8 bits represent functions within a single
69    /// endpoint.
70    ///
71    /// A device can distinguish Type 0 (local) from Type 1 (forwarded)
72    /// configuration cycles by comparing `target_bus` and `secondary_bus`:
73    /// when they are equal the access targets this device directly (Type 0),
74    /// otherwise it should be routed downstream (Type 1). An SR-IOV
75    /// capable device can use `secondary_bus` together with `target_bus` and
76    /// `function` to compute the VF number.
77    ///
78    /// The default implementation dispatches function 0 to
79    /// [`pci_cfg_write`](Self::pci_cfg_write) and silently drops writes to
80    /// other functions. Routing components (switches, bridges) and
81    /// multi-function devices should override this method.
82    ///
83    /// # Parameters
84    /// - `secondary_bus`: The secondary bus number of the downstream port
85    ///   that forwarded this access
86    /// - `target_bus`: The bus number targeted by the configuration access
87    /// - `function`: Device/function identifier — packed device/function on
88    ///   a legacy bus, or flat function number on PCIe
89    /// - `offset`: Configuration space offset
90    /// - `value`: Value to write
91    fn pci_cfg_write_with_routing(
92        &mut self,
93        secondary_bus: u8,
94        target_bus: u8,
95        function: u8,
96        offset: u16,
97        value: u32,
98    ) -> IoResult {
99        if secondary_bus == target_bus && function == 0 {
100            self.pci_cfg_write(offset, value)
101        } else {
102            IoResult::Ok
103        }
104    }
105
106    /// Check if the device has a suggested (bus, device, function) it expects
107    /// to be located at.
108    ///
109    /// The term "suggested" is important here, as it's important to note that
110    /// one of the major selling points of PCI was that PCI devices _shouldn't_
111    /// need to care about about what PCI address they are initialized at. i.e:
112    /// on a physical machine, it shouldn't matter that your fancy GTX 4090 is
113    /// plugged into the first vs. second PCI slot.
114    ///
115    /// ..that said, there are some instances where it makes sense for an
116    /// emulated device to declare its suggested PCI address:
117    ///
118    /// 1. Devices that emulate bespoke PCI devices part of a particular
119    ///    system's chipset.
120    ///   - e.g: the PIIX4 chipset includes several bespoke PCI devices that are
121    ///     required to have specific PCI addresses. While it _would_ be
122    ///     possible to relocate them to a different address, it may break OSes
123    ///     that assume they exist at those spec-declared addresses.
124    /// 2. Multi-function PCI devices
125    ///   - In an unfortunate case of inverted responsibilities, there is a
126    ///     single bit in the PCI configuration space's `Header` register that
127    ///     denotes if a particular PCI card includes multiple functions.
128    ///   - Since multi-function devices are pretty rare, `ChipsetDevice` opted
129    ///     to model each function as its own device, which in turn implies that
130    ///     in order to correctly init a multi-function PCI card, the
131    ///     `ChipsetDevice` with function 0 _must_ report if there are other
132    ///     functions at the same bus and device.
133    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
134        None
135    }
136}