pci_driver/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! A crate for developing user-space PCI and PCIe drivers.
4//!
5//! The driver development interface revolves around the [`PciDevice`](device::PciDevice) trait,
6//! which represents a PCI __function__ and allows you to:
7//!
8//! 1. Access its Configuration Space;
9//! 2. Access the regions defined by its Base Address Registers (BARs);
10//! 3. Access its Expansion ROM;
11//! 4. Add and remove mappings from the IOMMU that controls its DMA operations;
12//! 5. Configure its INTx, MSI, and MSI-X interrupt vectors;
13//! 6. Reset it.
14//!
15//! Implementations of this trait are called _backends_. For now, a single
16//! [`VfioPciDevice`](backends::vfio::VfioPciDevice) backend is provided, which relies on Linux's
17//! VFIO driver framework. The availability of this backend can be controlled through the `vfio`
18//! crate feature. Future backends will each have a corresponding feature. Note that the user cannot
19//! implement additional backends from outside this crate.
20//!
21//! This crate requires Rust 1.56 or above.
22//!
23//! The following sections showcase [`PciDevice`](device::PciDevice)'s features.
24//!
25//! ## Configuration space
26//!
27//! Calling [`PciDevice::config`](device::PciDevice::config) returns a
28//! [`PciConfig`](config::PciConfig) value, which provides access to the device's configuration
29//! space. Configuration space is made up of 8-bit, 16-bit, and 32-bit registers.
30//!
31//! Each register may represent a single numeric value (_e.g._, "Vendor ID") or be a bit field. Bit
32//! fields are composed of several independent bits (_e.g._, "Command") or sequences of bits
33//! (_e.g._, "Status"). In some cases, related registers are organized hierarchically into groups
34//! (_e.g._, "Class Code"). (The terms being used here might not match exactly the terminology of
35//! the PCI/PCIe specifications.)
36//!
37//! This crate provides "structured" access to each register, bit field, bit sequence, and bit using
38//! specialized accessor methods, so you don't have to remember details like the offsets of
39//! registers, masking and shifting for operating on bits, subtleties related to write-1-to-clear
40//! and reserved-zero bits, etc.
41//!
42//! Still, if you really want to, you can bypass all of this and just read and write directly at
43//! arbitrary offsets of the configuration space.
44//!
45//! The API also makes it easy to iterate over Capabilities and Extended Capabilities, and to find
46//! capabilities with specific Capability IDs, all while providing the same kind of structured
47//! access interface described above.
48//!
49//! Example usage:
50//!
51//! ```no_run
52//! use pci_driver::config::caps::{Capability, PciExpressCapability};
53//! use pci_driver::config::ext_caps::{ExtendedCapability, VendorSpecificExtendedCapability};
54//! use pci_driver::config::{PciClassCode, PciConfig};
55//! use pci_driver::device::PciDevice;
56//! use pci_driver::regions::{BackedByPciSubregion, PciRegion, PciRegionSnapshot};
57//!
58//! let device: &dyn PciDevice = unimplemented!();
59//!
60//! // Raw config space access
61//!
62//! let vendor_id: u16 = device.config().read_le_u16(0x00)?;
63//! let device_id: u16 = device.config().read_le_u16(0x02)?;
64//!
65//! // Structured config space access
66//!
67//! let device_id: u16 = device.config().device_id().read()?;
68//!
69//! let memory_space_enable: bool = device.config().command().memory_space_enable().read()?;
70//! device.config().command().memory_space_enable().write(true)?;
71//!
72//! device.config().status().master_data_parity_error().clear()?;
73//!
74//! let class_code: PciClassCode = device.config().class_code();
75//! let base_class_code: u8 = class_code.base_class_code().read()?;
76//! let sub_class_code: u8 = class_code.sub_class_code().read()?;
77//! let programming_interface: u8 = class_code.programming_interface().read()?;
78//!
79//! // Capabilities
80//!
81//! for cap in device.config().capabilities()? {
82//! // cap has type UnspecifiedCapability
83//! let cap_id: u8 = cap.header().capability_id().read()?;
84//! }
85//!
86//! let pcie_cap: Option<PciExpressCapability> = device
87//! .config()
88//! .capabilities()?
89//! .of_type::<PciExpressCapability>()?
90//! .next();
91//!
92//! if let Some(pcie_cap) = pcie_cap {
93//! println!("PCI Express device");
94//! let supports_flr: bool = pcie_cap
95//! .device_capabilities()
96//! .function_level_reset_capability()
97//! .read()?;
98//! } else {
99//! println!("Conventional PCI device");
100//! }
101//!
102//! // Extended capabilities
103//!
104//! for ext_cap in device.config().extended_capabilities()? {
105//! // cap has type UnspecifiedExtendedCapability
106//! let cap_id: u16 = ext_cap.header().capability_id().read()?;
107//! }
108//!
109//! let vendor_specific_ext_caps: Vec<VendorSpecificExtendedCapability> = device
110//! .config()
111//! .extended_capabilities()?
112//! .of_type::<VendorSpecificExtendedCapability>()?
113//! .collect();
114//!
115//! // Taking snapshot of entire config space, may improve performance if reading many registers
116//!
117//! let config_space_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(device.config())?;
118//! let device_id: u16 = config_space_snapshot.read_le_u16(0x02)?;
119//!
120//! let config_space: PciConfig = PciConfig::backed_by(&config_space_snapshot);
121//! let device_id: u16 = config_space.read_le_u16(0x02)?;
122//! let device_id: u16 = config_space.device_id().read()?;
123//! let memory_space_enable: bool = config_space.command().memory_space_enable().read()?;
124//!
125//! // Taking snapshot only of a specific capability
126//!
127//! let pcie_cap_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(
128//! config_space
129//! .capabilities()?
130//! .of_type::<PciExpressCapability>()?
131//! .next()
132//! .expect("not a PCIe device")
133//! )?;
134//!
135//! let pcie_cap = PciExpressCapability::backed_by(&pcie_cap_snapshot)?.unwrap();
136//! # std::io::Result::Ok(())
137//! ```
138//!
139//! ## BARs and Expansion ROM
140//!
141//! The [`PciDevice::bar`](device::PciDevice::bar) method can be used to retrieved an
142//! [`OwningPciRegion`](regions::OwningPciRegion) corresponding to a given Base Address Register
143//! (BAR) of the device. This value behaves similarly to an instance of
144//! [`PciConfig`](config::PciConfig), but does not provide the aforementioned "structured access"
145//! functionality, as BAR contents are device-specific. In addition,
146//! [`OwningPciRegion`](regions::OwningPciRegion) provides the ability to map the region onto
147//! process memory (if the region is mappable).
148//!
149//! A similar [`PciDevice::rom`](device::PciDevice::rom) method is also provided, giving access to
150//! the device's "Expansion ROM".
151//!
152//! Example usage:
153//!
154//! ```no_run
155//! use pci_driver::device::PciDevice;
156//! use pci_driver::regions::{MappedOwningPciRegion, OwningPciRegion, PciRegion, PciRegionSnapshot, Permissions};
157//!
158//! let device: &dyn PciDevice = unimplemented!();
159//!
160//! let bar_0: OwningPciRegion = device.bar(0).expect("expected device to have BAR 0");
161//! let rom: OwningPciRegion = device.rom().expect("expected device to have Expansion ROM");
162//!
163//! // Non-memory mapped access (always works, may be slower)
164//!
165//! assert!(bar_0.permissions().can_read());
166//! let value = bar_0.read_le_u32(0x20)?;
167//!
168//! // Memory-mapped access using `PciRegion` methods
169//!
170//! assert!(bar_0.permissions() == Permissions::ReadWrite);
171//! assert!(bar_0.is_mappable());
172//! let mapped_bar_0: MappedOwningPciRegion = bar_0.map(..4096, Permissions::Read)?;
173//!
174//! let value = mapped_bar_0.read_le_u32(0x20)?;
175//!
176//! // Memory-mapped access using raw pointers
177//!
178//! let value = u32::from_le(
179//! unsafe { mapped_bar_0.as_ptr().offset(0x20).cast::<u32>().read_volatile() }
180//! );
181//!
182//! // Taking snapshot of BAR 0
183//!
184//! let bar_0_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(&bar_0)?;
185//! # std::io::Result::Ok(())
186//! ```
187//!
188//! See [`pci_struct!` and `pci_bit_field!`](#pci_struct-and-pci_bit_field) further below to see how
189//! to easily create structured access APIs of your own, which you can use to access BARs and other
190//! regions with device-specific layouts.
191//!
192//! ## IOMMU
193//!
194//! The [`PciDevice::iommu`](device::PciDevice::iommu) method returns a
195//! [`PciIommu`](iommu::PciIommu) value, which can in turn be used to manipulate IOMMU mapping
196//! affecting the device.
197//!
198//! Example usage:
199//!
200//! ```no_run
201//! use pci_driver::device::PciDevice;
202//! use pci_driver::regions::Permissions;
203//!
204//! let device: &dyn PciDevice = unimplemented!();
205//!
206//! let iova: u64 = 0x12345678;
207//! let region_ptr: *const u8 = unimplemented!();
208//! let region_len: usize = 4096;
209//!
210//! unsafe { device.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite) };
211//! // ...
212//! unsafe { device.iommu().unmap(iova, region_len) };
213//! # std::io::Result::Ok(())
214//! ```
215//!
216//! ## Interrupts
217//!
218//! The [`PciDevice::interrupts`](device::PciDevice::interrupts) method returns a
219//! [`PciInterrupts`](interrupts::PciInterrupts) value, which provides control over the device's
220//! interrupt vectors. It allows you to associate specific interrupt vectors with eventfd
221//! descriptors, and to undo that association.
222//!
223//! Example usage:
224//!
225//! ```no_run
226//! use std::os::unix::io::RawFd;
227//! use pci_driver::device::PciDevice;
228//!
229//! let device: &dyn PciDevice = unimplemented!();
230//! let eventfds: &[RawFd] = unimplemented!();
231//!
232//! let max_enabled_intx_vectors = device.interrupts().intx().max();
233//! device.interrupts().intx().enable(eventfds)?;
234//! device.interrupts().intx().disable()?;
235//!
236//! let max_enabled_msi_vectors = device.interrupts().msi().max();
237//! device.interrupts().msi().enable(eventfds)?;
238//! device.interrupts().msi().disable()?;
239//!
240//! let max_enabled_msi_x_vectors = device.interrupts().msi_x().max();
241//! device.interrupts().msi_x().enable(eventfds)?;
242//! device.interrupts().msi_x().disable()?;
243//! # std::io::Result::Ok(())
244//! ```
245//!
246//! ## VFIO backend specificities
247//!
248//! In the following example, devices 0000:00:01.0 and 0000:00:02.0 belong to VFIO group 42, device
249//! 0000:00:03.0 to group 123.
250//!
251//! ```no_run
252//! use std::sync::Arc;
253//! use pci_driver::backends::vfio::{VfioContainer, VfioPciDevice};
254//! use pci_driver::device::PciDevice;
255//! use pci_driver::regions::Permissions;
256//!
257//! let container: Arc<VfioContainer> = Arc::new(VfioContainer::new(&[42, 123])?);
258//!
259//! let device_a = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:01.0", Arc::clone(&container))?;
260//! let device_b = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:02.0", Arc::clone(&container))?;
261//! let device_c = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:03.0", Arc::clone(&container))?;
262//!
263//! unsafe {
264//! let iova: u64 = 0x12345678;
265//! let region_ptr: *const u8 = unimplemented!();
266//! let region_len: usize = 4096;
267//!
268//! // All of the following calls are equivalent.
269//!
270//! container.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
271//!
272//! device_a.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
273//! device_b.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
274//! device_c.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
275//! }
276//!
277//! // Shorthand for when a device is the only one (that we care about) in its group, and the group
278//! // is the only one in its container
279//!
280//! let device = VfioPciDevice::open("/sys/bus/pci/devices/0000:00:01.0")?;
281//!
282//! // Resetting a PCI function, which may not be supported
283//!
284//! device.reset()?;
285//!
286//! // Resetting a whole container, which may also not be supported
287//!
288//! device.container().reset()?;
289//! # std::io::Result::Ok(())
290//! ```
291//!
292//! ## `pci_struct!` and `pci_bit_field!`
293//!
294//! Many times, your device's BARs or ROM will be structured into registers and bit fields similarly
295//! to configuration space, or there will be some capabilities that this crate doesn't provide a
296//! structured access API for, or even the vendor-specific contents of the Vendor Specific
297//! Capability will have some structure that this crate naturally can't capture.
298//!
299//! In C, you would usually cast pointers to overlay a `struct` on the memory corresponding to the
300//! region you want to access, and then use volatile accesses. You can do similar things in Rust,
301//! but that is unsafe, only works when the BAR or ROM is memory-mapped, and it can be error-prone
302//! to build the structs properly due to padding.
303//!
304//! A safe alternative is to have `read()` and `write()` functions that take an offset and a length,
305//! or are parameterized by the type you want to read or write. This crate provides that kind of
306//! access API through the [`PciRegion`](crate::regions::PciRegion) trait, but using it can be
307//! cumbersome and it is easy to pass in the wrong offset or read/write the wrong type. Things get
308//! even worse when you're bit fiddling to manipulate flags and applying write masks to preserve
309//! some bits, etc.
310//!
311//! To solve this, this crate provides the [`pci_struct!`](crate::pci_struct) and
312//! [`pci_bit_field!`](crate::pci_bit_field) macros, which you can use to easily define
313//! semantically-aware types that provide structured access to device regions and bit field
314//! registers. These are also used by the crate itself to define types like
315//! [`PciConfig`](crate::config::PciConfig) and [`PciStatus`](crate::config::PciStatus).
316//!
317//! Take [`PciClassCode`](crate::config::PciClassCode) as an example:
318//!
319//! ```no_run
320//! use pci_driver::pci_struct;
321//! use pci_driver::regions::structured::PciRegisterRo;
322//!
323//! pci_struct! {
324//! pub struct PciClassCode<'a> : 0x03 {
325//! base_class_code @ 0x00 : PciRegisterRo<'a, u8>,
326//! sub_class_code @ 0x01 : PciRegisterRo<'a, u8>,
327//! programming_interface @ 0x02 : PciRegisterRo<'a, u8>,
328//! }
329//! }
330//! ```
331//!
332//! Values of this type can be created using `PciClassCode::backed_by(subregion)`, where `subregion`
333//! is anything that implements `AsPciSubregion<'a>`, and the structure is taken to begin at the
334//! start of that subregion.
335//!
336//! Each field follows the format `name @ offset : type` and gives rise to a method with the given
337//! `name` that returns a value of the given `type`. The `offset` is in bytes from the start of the
338//! structure.
339//!
340//! [`PciConfig`](crate::config::PciConfig)'s definition is another good example:
341//!
342//! ```no_run
343//! use pci_driver::config::{PciClassCode, PciCommand, PciStatus};
344//! use pci_driver::pci_struct;
345//! use pci_driver::regions::structured::PciRegisterRo;
346//!
347//! pci_struct! {
348//! pub struct PciConfig<'a> {
349//! vendor_id @ 0x00 : PciRegisterRo<'a, u16>,
350//! device_id @ 0x02 : PciRegisterRo<'a, u16>,
351//! command @ 0x04 : PciCommand<'a>,
352//! status @ 0x06 : PciStatus<'a>,
353//! revision_id @ 0x08 : PciRegisterRo<'a, u8>,
354//! class_code @ 0x09 : PciClassCode<'a>,
355//! // ... more fields ...
356//! }
357//! }
358//! ```
359//!
360//! Note that one of the fields is actually of the type we defined above: `PciClassCode`. We also
361//! specify an offset for it, which will serve as the base offset for the fields that it in turn
362//! contains.
363//!
364//! Note also the "Command" and "Status" fields. These are _bit fields_. Here's how
365//! [`PciStatus`](crate::config::PciStatus) is defined:
366//!
367//! ```no_run
368//! use pci_driver::pci_bit_field;
369//!
370//! pci_bit_field! {
371//! pub struct PciStatus<'a> : RW u16 {
372//! immediate_readiness @ 0 : RO,
373//! __ @ 1--2 : RsvdZ,
374//! interrupt_status @ 3 : RO,
375//! capabilities_list @ 4 : RO,
376//! mhz_66_capable @ 5 : RO,
377//! __ @ 6 : RsvdZ,
378//! fast_back_to_back_transactions_capable @ 7 : RO,
379//! master_data_parity_error @ 8 : RW1C,
380//! devsel_timing @ 9--10 : RO u8,
381//! signaled_target_abort @ 11 : RW1C,
382//! received_target_abort @ 12 : RW1C,
383//! received_master_abort @ 13 : RW1C,
384//! signaled_system_error @ 14 : RW1C,
385//! detected_parity_error @ 15 : RW1C,
386//! }
387//! }
388//! ```
389//!
390//! Values of this type can be created using `PciStatus::backed_by(subregion)`, exactly like types
391//! defined using `pci_struct!`. The bit field is taken to be at the start of the given subregion.
392//!
393//! `PciStatus`'s definition also follows the same general scheme as if using `pci_struct!`, but now
394//! each line represents a bit or set of bits in a register. First, note the `: RW u16` after the
395//! struct name: this means that the struct represents a read-write register that is 16 bits wide.
396//!
397//! Then, we have the "Immediate Readiness" bit at position 0, i.e., the lowest-order bit in the
398//! register. It is read-only, hence the `RO`. The format for each bit is `name @ bit : mode`, while
399//! for sets of more than 1 bit it is `name @ first_bit--last_bit : mode` (`first_bit` and
400//! `last_bit` are inclusive).
401//!
402//! Then we have a `__` line with mode `RsvdZ` that represents 2 consecutive bits. The `RsvdZ`
403//! terminology comes from the PCI/PCIe specifications, and means that when writing to the register
404//! as a whole, these bits must always be written as 0. There's also a `RsvdP` mode which means that
405//! the affected bits must be written exactly how they currently read. (These two modes exist for
406//! forward-compatibility purposes).
407//!
408//! A few lines down, we get to the "Master Data Parity Error" bit, which has mode `RW1C`. This
409//! means that the bit can be read as usual, and it can be _cleared_ (i.e., made to be 0), but it
410//! cannot be _set_ (made to be 1), so the bit isn't quite read-write. (RW1C once again comes from
411//! the PCI/PCIe specifications and approximately stands for Read-or-Write-1-to-Clear.) There's also
412//! plain `RW` bits, which can be freely read, cleared, and set, and are not showcased in this
413//! example.
414//!
415//! And finally, let's look at "DEVSEL Timing", which occupies bits 9 and 10 and has mode `RO u8`.
416//! This is a set of two bits which may only be read, not written, and which reads back as an `u8`
417//! (it could also have been `u16` or `u32`).
418//!
419//! In all these cases, the name of the field gives rise to a method that returns a value that
420//! allows you to inspect (and possibly manipulate) the bit or set of bits. (Note that the `name` of
421//! the field is ignored for `RsvdZ` and `RsvdP` bits, but it has to be there. It cannot be a single
422//! `_` as that is not an identifier, so we use `__` instead.)
423//!
424//! You don't have to cover every bit in the register, although we do so in the example above.
425//! Leaving bits unspecified is equivalent to specifying them as `RsvdP`.
426//!
427//! Finally, note that when using `pci_struct!` and `pci_bit_field!`, you can add doc comments both
428//! to the struct or bit field type itself, and to each of their fields or bits.
429
430/* ---------------------------------------------------------------------------------------------- */
431
432#![cfg_attr(feature = "_unsafe-op-in-unsafe-fn", deny(unsafe_op_in_unsafe_fn))]
433#![cfg_attr(not(feature = "_unsafe-op-in-unsafe-fn"), allow(unused_unsafe))]
434
435// TODO: enable:
436// #![warn(missing_docs)]
437
438pub mod backends;
439pub mod config;
440pub mod device;
441pub mod interrupts;
442pub mod iommu;
443pub mod regions;
444
445/* ---------------------------------------------------------------------------------------------- */