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}