1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// SPDX-License-Identifier: MIT OR Apache-2.0

//! A crate for developing user-space PCI and PCIe drivers.
//!
//! The driver development interface revolves around the [`PciDevice`](device::PciDevice) trait,
//! which represents a PCI __function__ and allows you to:
//!
//! 1. Access its Configuration Space;
//! 2. Access the regions defined by its Base Address Registers (BARs);
//! 3. Access its Expansion ROM;
//! 4. Add and remove mappings from the IOMMU that controls its DMA operations;
//! 5. Configure its INTx, MSI, and MSI-X interrupt vectors;
//! 6. Reset it.
//!
//! Implementations of this trait are called _backends_. For now, a single
//! [`VfioPciDevice`](backends::vfio::VfioPciDevice) backend is provided, which relies on Linux's
//! VFIO driver framework. The availability of this backend can be controlled through the `vfio`
//! crate feature. Future backends will each have a corresponding feature. Note that the user cannot
//! implement additional backends from outside this crate.
//!
//! This crate requires Rust 1.47 or above.
//!
//! The following sections showcase [`PciDevice`](device::PciDevice)'s features.
//!
//! ## Configuration space
//!
//! Calling [`PciDevice::config`](device::PciDevice::config) returns a
//! [`PciConfig`](config::PciConfig) value, which provides access to the device's configuration
//! space. Configuration space is made up of 8-bit, 16-bit, and 32-bit registers.
//!
//! Each register may represent a single numeric value (_e.g._, "Vendor ID") or be a bit field. Bit
//! fields are composed of several independent bits (_e.g._, "Command") or sequences of bits
//! (_e.g._, "Status"). In some cases, related registers are organized hierarchically into groups
//! (_e.g._, "Class Code"). (The terms being used here might not match exactly the terminology of
//! the PCI/PCIe specifications.)
//!
//! This crate provides "structured" access to each register, bit field, bit sequence, and bit using
//! specialized accessor methods, so you don't have to remember details like the offsets of
//! registers, masking and shifting for operating on bits, subtleties related to write-1-to-clear
//! and reserved-zero bits, etc.
//!
//! Still, if you really want to, you can bypass all of this and just read and write directly at
//! arbitrary offsets of the configuration space.
//!
//! The API also makes it easy to iterate over Capabilities and Extended Capabilities, and to find
//! capabilities with specific Capability IDs, all while providing the same kind of structured
//! access interface described above.
//!
//! Example usage:
//!
//! ```no_run
//! use pci_driver::config::caps::{Capability, PciExpressCapability};
//! use pci_driver::config::ext_caps::{ExtendedCapability, VendorSpecificExtendedCapability};
//! use pci_driver::config::{PciClassCode, PciConfig};
//! use pci_driver::device::PciDevice;
//! use pci_driver::regions::{BackedByPciSubregion, PciRegion, PciRegionSnapshot};
//!
//! let device: &dyn PciDevice = unimplemented!();
//!
//! // Raw config space access
//!
//! let vendor_id: u16 = device.config().read_le_u16(0x00)?;
//! let device_id: u16 = device.config().read_le_u16(0x02)?;
//!
//! // Structured config space access
//!
//! let device_id: u16 = device.config().device_id().read()?;
//!
//! let memory_space_enable: bool = device.config().command().memory_space_enable().read()?;
//! device.config().command().memory_space_enable().write(true)?;
//!
//! device.config().status().master_data_parity_error().clear()?;
//!
//! let class_code: PciClassCode = device.config().class_code();
//! let base_class_code: u8 = class_code.base_class_code().read()?;
//! let sub_class_code: u8 = class_code.sub_class_code().read()?;
//! let programming_interface: u8 = class_code.programming_interface().read()?;
//!
//! // Capabilities
//!
//! for cap in device.config().capabilities()? {
//!     // cap has type UnspecifiedCapability
//!     let cap_id: u8 = cap.header().capability_id().read()?;
//! }
//!
//! let pcie_cap: Option<PciExpressCapability> = device
//!     .config()
//!     .capabilities()?
//!     .of_type::<PciExpressCapability>()?
//!     .next();
//!
//! if let Some(pcie_cap) = pcie_cap {
//!     println!("PCI Express device");
//!     let supports_flr: bool = pcie_cap
//!         .device_capabilities()
//!         .function_level_reset_capability()
//!         .read()?;
//! } else {
//!     println!("Conventional PCI device");
//! }
//!
//! // Extended capabilities
//!
//! for ext_cap in device.config().extended_capabilities()? {
//!     // cap has type UnspecifiedExtendedCapability
//!     let cap_id: u16 = ext_cap.header().capability_id().read()?;
//! }
//!
//! let vendor_specific_ext_caps: Vec<VendorSpecificExtendedCapability> = device
//!     .config()
//!     .extended_capabilities()?
//!     .of_type::<VendorSpecificExtendedCapability>()?
//!     .collect();
//!
//! // Taking snapshot of entire config space, may improve performance if reading many registers
//!
//! let config_space_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(device.config())?;
//! let device_id: u16 = config_space_snapshot.read_le_u16(0x02)?;
//!
//! let config_space: PciConfig = PciConfig::backed_by(&config_space_snapshot);
//! let device_id: u16 = config_space.read_le_u16(0x02)?;
//! let device_id: u16 = config_space.device_id().read()?;
//! let memory_space_enable: bool = config_space.command().memory_space_enable().read()?;
//!
//! // Taking snapshot only of a specific capability
//!
//! let pcie_cap_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(
//!     config_space
//!         .capabilities()?
//!         .of_type::<PciExpressCapability>()?
//!         .next()
//!         .expect("not a PCIe device")
//! )?;
//!
//! let pcie_cap = PciExpressCapability::backed_by(&pcie_cap_snapshot)?.unwrap();
//! # std::io::Result::Ok(())
//! ```
//!
//! ## BARs and Expansion ROM
//!
//! The [`PciDevice::bar`](device::PciDevice::bar) method can be used to retrieved an
//! [`OwningPciRegion`](regions::OwningPciRegion) corresponding to a given Base Address Register
//! (BAR) of the device. This value behaves similarly to an instance of
//! [`PciConfig`](config::PciConfig), but does not provide the aforementioned "structured access"
//! functionality, as BAR contents are device-specific. In addition,
//! [`OwningPciRegion`](regions::OwningPciRegion) provides the ability to map the region onto
//! process memory (if the region is mappable).
//!
//! A similar [`PciDevice::rom`](device::PciDevice::rom) method is also provided, giving access to
//! the device's "Expansion ROM".
//!
//! Example usage:
//!
//! ```no_run
//! use pci_driver::device::PciDevice;
//! use pci_driver::regions::{MappedOwningPciRegion, OwningPciRegion, PciRegion, PciRegionSnapshot, Permissions};
//!
//! let device: &dyn PciDevice = unimplemented!();
//!
//! let bar_0: OwningPciRegion = device.bar(0).expect("expected device to have BAR 0");
//! let rom: OwningPciRegion = device.rom().expect("expected device to have Expansion ROM");
//!
//! // Non-memory mapped access (always works, may be slower)
//!
//! assert!(bar_0.permissions().can_read());
//! let value = bar_0.read_le_u32(0x20)?;
//!
//! // Memory-mapped access using `PciRegion` methods
//!
//! assert!(bar_0.permissions() == Permissions::ReadWrite);
//! assert!(bar_0.is_mappable());
//! let mapped_bar_0: MappedOwningPciRegion = bar_0.map(..4096, Permissions::Read)?;
//!
//! let value = mapped_bar_0.read_le_u32(0x20)?;
//!
//! // Memory-mapped access using raw pointers
//!
//! let value = u32::from_le(
//!     unsafe { mapped_bar_0.as_ptr().offset(0x20).cast::<u32>().read_volatile() }
//! );
//!
//! // Taking snapshot of BAR 0
//!
//! let bar_0_snapshot: PciRegionSnapshot = PciRegionSnapshot::take(&bar_0)?;
//! # std::io::Result::Ok(())
//! ```
//!
//! See [`pci_struct!` and `pci_bit_field!`](#pci_struct-and-pci_bit_field) further below to see how
//! to easily create structured access APIs of your own, which you can use to access BARs and other
//! regions with device-specific layouts.
//!
//! ## IOMMU
//!
//! The [`PciDevice::iommu`](device::PciDevice::iommu) method returns a
//! [`PciIommu`](iommu::PciIommu) value, which can in turn be used to manipulate IOMMU mapping
//! affecting the device.
//!
//! Example usage:
//!
//! ```no_run
//! use pci_driver::device::PciDevice;
//! use pci_driver::regions::Permissions;
//!
//! let device: &dyn PciDevice = unimplemented!();
//!
//! let iova: u64 = 0x12345678;
//! let region_ptr: *const u8 = unimplemented!();
//! let region_len: usize = 4096;
//!
//! unsafe { device.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite) };
//! // ...
//! unsafe { device.iommu().unmap(iova, region_len) };
//! # std::io::Result::Ok(())
//! ```
//!
//! ## Interrupts
//!
//! The [`PciDevice::interrupts`](device::PciDevice::interrupts) method returns a
//! [`PciInterrupts`](interrupts::PciInterrupts) value, which provides control over the device's
//! interrupt vectors. It allows you to associate specific interrupt vectors with eventfd
//! descriptors, and to undo that association.
//!
//! Example usage:
//!
//! ```no_run
//! use std::os::unix::io::RawFd;
//! use pci_driver::device::PciDevice;
//!
//! let device: &dyn PciDevice = unimplemented!();
//! let eventfds: &[RawFd] = unimplemented!();
//!
//! let max_enabled_intx_vectors = device.interrupts().intx().max();
//! device.interrupts().intx().enable(eventfds)?;
//! device.interrupts().intx().disable()?;
//!
//! let max_enabled_msi_vectors = device.interrupts().msi().max();
//! device.interrupts().msi().enable(eventfds)?;
//! device.interrupts().msi().disable()?;
//!
//! let max_enabled_msi_x_vectors = device.interrupts().msi_x().max();
//! device.interrupts().msi_x().enable(eventfds)?;
//! device.interrupts().msi_x().disable()?;
//! # std::io::Result::Ok(())
//! ```
//!
//! ## VFIO backend specificities
//!
//! In the following example, devices 0000:00:01.0 and 0000:00:02.0 belong to VFIO group 42, device
//! 0000:00:03.0 to group 123.
//!
//! ```no_run
//! use std::sync::Arc;
//! use pci_driver::backends::vfio::{VfioContainer, VfioPciDevice};
//! use pci_driver::device::PciDevice;
//! use pci_driver::regions::Permissions;
//!
//! let container: Arc<VfioContainer> = Arc::new(VfioContainer::new(&[42, 123])?);
//!
//! let device_a = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:01.0", Arc::clone(&container))?;
//! let device_b = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:02.0", Arc::clone(&container))?;
//! let device_c = VfioPciDevice::open_in_container("/sys/bus/pci/devices/0000:00:03.0", Arc::clone(&container))?;
//!
//! unsafe {
//!     let iova: u64 = 0x12345678;
//!     let region_ptr: *const u8 = unimplemented!();
//!     let region_len: usize = 4096;
//!
//!     // All of the following calls are equivalent.
//!
//!     container.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
//!
//!     device_a.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
//!     device_b.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
//!     device_c.iommu().map(iova, region_len, region_ptr, Permissions::ReadWrite);
//! }
//!
//! // Shorthand for when a device is the only one (that we care about) in its group, and the group
//! // is the only one in its container
//!
//! let device = VfioPciDevice::open("/sys/bus/pci/devices/0000:00:01.0")?;
//!
//! // Resetting a PCI function, which may not be supported
//!
//! device.reset()?;
//!
//! // Resetting a whole container, which may also not be supported
//!
//! device.container().reset()?;
//! # std::io::Result::Ok(())
//! ```
//!
//! ## `pci_struct!` and `pci_bit_field!`
//!
//! Many times, your device's BARs or ROM will be structured into registers and bit fields similarly
//! to configuration space, or there will be some capabilities that this crate doesn't provide a
//! structured access API for, or even the vendor-specific contents of the Vendor Specific
//! Capability will have some structure that this crate naturally can't capture.
//!
//! In C, you would usually cast pointers to overlay a `struct` on the memory corresponding to the
//! region you want to access, and then use volatile accesses. You can do similar things in Rust,
//! but that is unsafe, only works when the BAR or ROM is memory-mapped, and it can be error-prone
//! to build the structs properly due to padding.
//!
//! A safe alternative is to have `read()` and `write()` functions that take an offset and a length,
//! or are parameterized by the type you want to read or write. This crate provides that kind of
//! access API through the [`PciRegion`](crate::regions::PciRegion) trait, but using it can be
//! cumbersome and it is easy to pass in the wrong offset or read/write the wrong type. Things get
//! even worse when you're bit fiddling to manipulate flags and applying write masks to preserve
//! some bits, etc.
//!
//! To solve this, this crate provides the [`pci_struct!`](crate::pci_struct) and
//! [`pci_bit_field!`](crate::pci_bit_field) macros, which you can use to easily define
//! semantically-aware types that provide structured access to device regions and bit field
//! registers. These are also used by the crate itself to define types like
//! [`PciConfig`](crate::config::PciConfig) and [`PciStatus`](crate::config::PciStatus).
//!
//! Take [`PciClassCode`](crate::config::PciClassCode) as an example:
//!
//! ```no_run
//! use pci_driver::pci_struct;
//! use pci_driver::regions::structured::PciRegisterRo;
//!
//! pci_struct! {
//!     pub struct PciClassCode<'a> : 0x03 {
//!         base_class_code       @ 0x00 : PciRegisterRo<'a, u8>,
//!         sub_class_code        @ 0x01 : PciRegisterRo<'a, u8>,
//!         programming_interface @ 0x02 : PciRegisterRo<'a, u8>,
//!     }
//! }
//! ```
//!
//! Values of this type can be created using `PciClassCode::backed_by(subregion)`, where `subregion`
//! is anything that implements `AsPciSubregion<'a>`, and the structure is taken to begin at the
//! start of that subregion.
//!
//! Each field follows the format `name @ offset : type` and gives rise to a method with the given
//! `name` that returns a value of the given `type`. The `offset` is in bytes from the start of the
//! structure.
//!
//! [`PciConfig`](crate::config::PciConfig)'s definition is another good example:
//!
//! ```no_run
//! use pci_driver::config::{PciClassCode, PciCommand, PciStatus};
//! use pci_driver::pci_struct;
//! use pci_driver::regions::structured::PciRegisterRo;
//!
//! pci_struct! {
//!     pub struct PciConfig<'a> {
//!         vendor_id   @ 0x00 : PciRegisterRo<'a, u16>,
//!         device_id   @ 0x02 : PciRegisterRo<'a, u16>,
//!         command     @ 0x04 : PciCommand<'a>,
//!         status      @ 0x06 : PciStatus<'a>,
//!         revision_id @ 0x08 : PciRegisterRo<'a, u8>,
//!         class_code  @ 0x09 : PciClassCode<'a>,
//!         // ... more fields ...
//!     }
//! }
//! ```
//!
//! Note that one of the fields is actually of the type we defined above: `PciClassCode`. We also
//! specify an offset for it, which will serve as the base offset for the fields that it in turn
//! contains.
//!
//! Note also the "Command" and "Status" fields. These are _bit fields_. Here's how
//! [`PciStatus`](crate::config::PciStatus) is defined:
//!
//! ```no_run
//! use pci_driver::pci_bit_field;
//!
//! pci_bit_field! {
//!     pub struct PciStatus<'a> : RW u16 {
//!         immediate_readiness                    @     0 : RO,
//!         __                                     @  1--2 : RsvdZ,
//!         interrupt_status                       @     3 : RO,
//!         capabilities_list                      @     4 : RO,
//!         mhz_66_capable                         @     5 : RO,
//!         __                                     @     6 : RsvdZ,
//!         fast_back_to_back_transactions_capable @     7 : RO,
//!         master_data_parity_error               @     8 : RW1C,
//!         devsel_timing                          @ 9--10 : RO u8,
//!         signaled_target_abort                  @    11 : RW1C,
//!         received_target_abort                  @    12 : RW1C,
//!         received_master_abort                  @    13 : RW1C,
//!         signaled_system_error                  @    14 : RW1C,
//!         detected_parity_error                  @    15 : RW1C,
//!     }
//! }
//! ```
//!
//! Values of this type can be created using `PciStatus::backed_by(subregion)`, exactly like types
//! defined using `pci_struct!`. The bit field is taken to be at the start of the given subregion.
//!
//! `PciStatus`'s definition also follows the same general scheme as if using `pci_struct!`, but now
//! each line represents a bit or set of bits in a register. First, note the `: RW u16` after the
//! struct name: this means that the struct represents a read-write register that is 16 bits wide.
//!
//! Then, we have the "Immediate Readiness" bit at position 0, i.e., the lowest-order bit in the
//! register. It is read-only, hence the `RO`. The format for each bit is `name @ bit : mode`, while
//! for sets of more than 1 bit it is `name @ first_bit--last_bit : mode` (`first_bit` and
//! `last_bit` are inclusive).
//!
//! Then we have a `__` line with mode `RsvdZ` that represents 2 consecutive bits. The `RsvdZ`
//! terminology comes from the PCI/PCIe specifications, and means that when writing to the register
//! as a whole, these bits must always be written as 0. There's also a `RsvdP` mode which means that
//! the affected bits must be written exactly how they currently read. (These two modes exist for
//! forward-compatibility purposes).
//!
//! A few lines down, we get to the "Master Data Parity Error" bit, which has mode `RW1C`. This
//! means that the bit can be read as usual, and it can be _cleared_ (i.e., made to be 0), but it
//! cannot be _set_ (made to be 1), so the bit isn't quite read-write. (RW1C once again comes from
//! the PCI/PCIe specifications and approximately stands for Read-or-Write-1-to-Clear.) There's also
//! plain `RW` bits, which can be freely read, cleared, and set, and are not showcased in this
//! example.
//!
//! And finally, let's look at "DEVSEL Timing", which occupies bits 9 and 10 and has mode `RO u8`.
//! This is a set of two bits which may only be read, not written, and which reads back as an `u8`
//! (it could also have been `u16` or `u32`).
//!
//! In all these cases, the name of the field gives rise to a method that returns a value that
//! allows you to inspect (and possibly manipulate) the bit or set of bits. (Note that the `name` of
//! the field is ignored for `RsvdZ` and `RsvdP` bits, but it has to be there. It cannot be a single
//! `_` as that is not an identifier, so we use `__` instead.)
//!
//! You don't have to cover every bit in the register, although we do so in the example above.
//! Leaving bits unspecified is equivalent to specifying them as `RsvdP`.
//!
//! Finally, note that when using `pci_struct!` and `pci_bit_field!`, you can add doc comments both
//! to the struct or bit field type itself, and to each of their fields or bits.

/* ---------------------------------------------------------------------------------------------- */

#![cfg_attr(feature = "_unsafe-op-in-unsafe-fn", deny(unsafe_op_in_unsafe_fn))]
#![cfg_attr(not(feature = "_unsafe-op-in-unsafe-fn"), allow(unused_unsafe))]

// TODO: enable:
// #![warn(missing_docs)]

pub mod backends;
pub mod config;
pub mod device;
pub mod interrupts;
pub mod iommu;
pub mod regions;

/* ---------------------------------------------------------------------------------------------- */