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}