pci_x86/device.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5use tock_registers::{register_bitfields, LocalRegisterCopy};
6
7use super::cfg::{self, offset};
8use super::Bdf;
9
10register_bitfields![u16,
11 /// PCI Command register bitfields
12 pub Command [
13 /// I/O space accesses enabled
14 IO_SPACE OFFSET(0) NUMBITS(1) [],
15
16 /// Memory space accesses enabled
17 MEM_SPACE OFFSET(1) NUMBITS(1) [],
18
19 /// Device is allowed to act as a bus master
20 BUS_MASTER OFFSET(2) NUMBITS(1) [],
21
22 /// Monitor/Special cycles on PCI bus
23 SPECIAL_CYCLES OFFSET(3) NUMBITS(1) [],
24
25 /// Memory Write and Invalidate enable
26 MEM_WRITE_INV OFFSET(4) NUMBITS(1) [],
27
28 /// VGA palette snoop enable
29 VGA_PALETTE OFFSET(5) NUMBITS(1) [],
30
31 /// Parity error response enable
32 PARITY_ERR_RESP OFFSET(6) NUMBITS(1) [],
33
34 /// SERR# driver enable
35 SERR_ENABLE OFFSET(8) NUMBITS(1) [],
36
37 /// Fast back-to-back transactions enable
38 FAST_BACK_TO_BACK OFFSET(9) NUMBITS(1) [],
39
40 /// Interrupt disable
41 INT_DISABLE OFFSET(10) NUMBITS(1) [],
42 ],
43
44 /// PCI Status register bitfields
45 pub Status [
46 /// Interrupt status (pending)
47 INT_STATUS OFFSET(3) NUMBITS(1) [],
48
49 /// Capabilities list present
50 CAP_LIST OFFSET(4) NUMBITS(1) [],
51
52 /// 66 MHz capable
53 CAP_66MHZ OFFSET(5) NUMBITS(1) [],
54
55 /// Fast back-to-back capable
56 FAST_BACK_TO_BACK_CAPABLE OFFSET(7) NUMBITS(1) [],
57
58 /// Master data parity error detected
59 MASTER_DATA_PARITY_ERROR OFFSET(8) NUMBITS(1) [],
60
61 /// DEVSEL timing encoding
62 DEVSEL OFFSET(9) NUMBITS(2) [],
63
64 /// Signaled target abort
65 SIGNALED_TARGET_ABORT OFFSET(11) NUMBITS(1) [],
66
67 /// Received target abort
68 RECEIVED_TARGET_ABORT OFFSET(12) NUMBITS(1) [],
69
70 /// Received master abort
71 RECEIVED_MASTER_ABORT OFFSET(13) NUMBITS(1) [],
72
73 /// Signaled system error (SERR#)
74 SIGNALED_SYSTEM_ERROR OFFSET(14) NUMBITS(1) [],
75
76 /// Detected parity error
77 DETECTED_PARITY_ERROR OFFSET(15) NUMBITS(1) [],
78 ]
79];
80
81/// Type-safe representation of PCI command register value
82pub type CommandVal = LocalRegisterCopy<u16, Command::Register>;
83
84/// Type-safe representation of PCI status register value
85pub type StatusVal = LocalRegisterCopy<u16, Status::Register>;
86
87/// Representation of a PCI device
88///
89/// This struct provides low-level methods for directly reading and writing
90/// values from the PCI configuration space for this device, as well as
91/// higer-level methods for accessing standard fields.
92///
93/// If you know the BDF of the device you want to access, you can directly create a [`Device`]
94/// instance and use it to interact with the device:
95///
96/// ```ignore
97/// let bdf = Bdf::new(0, 1, 0);
98/// let dev = Device::new(bdf);
99///
100/// // Check vendor and device ID:
101/// let vid = dev.vendor_id();
102/// let did = dev.device_id();
103/// if vid == 0x1234 && did == 0x5678 {
104/// // Found the device we were looking for!
105/// }
106/// ```
107///
108/// Alternatively, you can use the [`iter`][crate::iter] function to enumerate all PCI devices in
109/// the system. This method automatically filters out non-existent devices, and it returns an
110/// iterator which can be chained like any other Rust iterator:
111///
112/// ```ignore
113/// let dev = pci::iter().find(|d| d.vendor_id() == 0x1234 && d.device_id() == 0x5678);
114/// if dev.is_none() {
115/// // No such device found in the system.
116/// }
117/// ```
118#[derive(Copy, Clone, Debug, Eq, PartialEq)]
119pub struct Device {
120 bdf: Bdf,
121}
122
123impl Device {
124 /// Constructs a new `Device` instance from a given BDF identifier.
125 pub const fn new(bdf: Bdf) -> Self {
126 Self { bdf }
127 }
128
129 /// Reads an 8-bit value from this device's PCI configuration space.
130 #[inline]
131 pub fn read8(&self, offset: u16) -> u8 {
132 cfg::read8(self.bdf, offset)
133 }
134
135 /// Writes an 8-bit value to this device's PCI configuration space.
136 #[inline]
137 pub fn write8(&self, offset: u16, val: u8) {
138 cfg::write8(self.bdf, offset, val)
139 }
140
141 /// Reads a 16-bit value from this device's PCI configuration space.
142 #[inline]
143 pub fn read16(&self, offset: u16) -> u16 {
144 cfg::read16(self.bdf, offset)
145 }
146
147 /// Writes a 16-bit value to this device's PCI configuration space.
148 #[inline]
149 pub fn write16(&self, offset: u16, val: u16) {
150 cfg::write16(self.bdf, offset, val)
151 }
152
153 /// Reads a 32-bit value from this device's PCI configuration space.
154 #[inline]
155 pub fn read32(&self, offset: u16) -> u32 {
156 cfg::read32(self.bdf, offset)
157 }
158
159 /// Writes a 32-bit value to this device's PCI configuration space.
160 #[inline]
161 pub fn write32(&self, offset: u16, val: u32) {
162 cfg::write32(self.bdf, offset, val)
163 }
164
165 /// Reads and returns the PCI vendor ID.
166 #[inline]
167 pub fn vendor_id(&self) -> u16 {
168 self.read16(offset::VENDOR_ID)
169 }
170
171 /// Reads and returns the PCI device ID.
172 #[inline]
173 pub fn device_id(&self) -> u16 {
174 self.read16(offset::DEVICE_ID)
175 }
176
177 /// Reads the command register of this device.
178 #[inline]
179 pub fn command(&self) -> CommandVal {
180 let value = self.read16(offset::COMMAND);
181 LocalRegisterCopy::new(value)
182 }
183
184 /// Sets the command register of this device.
185 #[inline]
186 pub fn set_command(&self, value: CommandVal) {
187 self.write16(offset::COMMAND, value.get());
188 }
189
190 /// Reads the status register of this device.
191 #[inline]
192 pub fn status(&self) -> StatusVal {
193 let value = self.read16(offset::STATUS);
194 LocalRegisterCopy::new(value)
195 }
196
197 /// Reset fields within status register of this device.
198 ///
199 /// The PCI status register implements "write 1 to reset" behavior for certain fields. This
200 /// method may be used to reset such fields.
201 #[inline]
202 pub fn reset_status(&self, value: StatusVal) {
203 self.write16(offset::STATUS, value.get());
204 }
205
206 /// Reads the header type of this device.
207 #[inline]
208 pub fn header_type(&self) -> u8 {
209 self.read8(offset::HEADER_TYPE)
210 }
211
212 pub fn bar(&self, index: usize) -> Option<u32> {
213 if index > 5 {
214 return None;
215 }
216 let off: u16 = offset::BAR0 + (index as u16) * 4u16;
217 Some(self.read32(off))
218 }
219
220 pub fn set_bar(&self, index: usize, val: u32) {
221 if index > 5 {
222 return;
223 }
224 let off: u16 = offset::BAR0 + (index as u16) * 4u16;
225 self.write32(off, val)
226 }
227
228 /// Decodes the BAR at `index` and returns its memory address.
229 ///
230 /// - Returns `None` if `index` is out of range, the BAR is an I/O BAR,
231 /// or the BAR type is unsupported.
232 /// - For 64-bit memory BARs, this will read the high dword from the next
233 /// BAR and combine them.
234 pub fn bar_addr(&self, index: u8) -> Option<usize> {
235 if index > 5 {
236 return None;
237 }
238
239 let v = self.bar(index as usize)?;
240
241 // I/O BARs not supported here
242 if (v & 0x1) != 0 {
243 return None;
244 }
245
246 let typ = (v >> 1) & 0x3;
247 match typ {
248 // 32-bit MMIO BAR
249 0 => Some((v & 0xFFFF_FFF0) as usize),
250
251 // 64-bit MMIO BAR (consumes the next BAR as high dword)
252 2 => {
253 if (index as usize) + 1 > 5 {
254 return None;
255 }
256 let low = (v & 0xFFFF_FFF0) as u64;
257 let high = self.bar((index as usize) + 1)? as u64;
258 let addr = ((high << 32) | low) as usize;
259 Some(addr)
260 }
261
262 // Unsupported types
263 _ => None,
264 }
265 }
266
267 /// Returns the offset of the first capability pointer, if present.
268 pub fn cap_ptr(&self) -> Option<u8> {
269 // Check status register to see whether cap_ptr is valid (Capabilities List bit)
270 if !self.status().is_set(Status::CAP_LIST) {
271 return None;
272 }
273
274 let ptr = self.read8(offset::CAP_PTR);
275 if ptr == 0 {
276 None
277 } else {
278 Some(ptr)
279 }
280 }
281
282 /// Iterate over this device's capabilities list.
283 pub fn capabilities(&self) -> super::cap::CapIter<'_> {
284 super::cap::CapIter::new(self)
285 }
286
287 /// Returns the interrupt line assigned to this device, if applicable.
288 #[inline]
289 pub fn int_line(&self) -> Option<u8> {
290 // Config register only exists for normal devices
291 if self.header_type() != 0 {
292 return None;
293 }
294
295 let val = self.read8(offset::INT_LINE);
296
297 // Per the spec, 0xFF indicates the interrupt line is disconnected
298 if val == 0xFF {
299 return None;
300 }
301
302 Some(val)
303 }
304}