5 个版本

0.1.4 2023 年 8 月 2 日
0.1.3 2023 年 3 月 21 日
0.1.2 2022 年 10 月 15 日
0.1.1 2022 年 9 月 10 日
0.1.0 2022 年 8 月 25 日

219硬件支持

Download history 50/week @ 2024-04-20 133/week @ 2024-04-27 132/week @ 2024-05-04 75/week @ 2024-05-11 67/week @ 2024-05-18 117/week @ 2024-05-25 58/week @ 2024-06-01 57/week @ 2024-06-08 62/week @ 2024-06-15 52/week @ 2024-06-22 82/week @ 2024-06-29 32/week @ 2024-07-06 52/week @ 2024-07-13 53/week @ 2024-07-20 68/week @ 2024-07-27 32/week @ 2024-08-03

210 每月下载量
用于 2 crate

MIT/Apache

230KB
4K SLoC

pci-driver

pci-driver 是一个Rust crate,它允许您开发用户空间的PCI和PCIe驱动程序。它目前使用Linux的VFIO来实现,但设计上可以扩展到将来的其他"后端"。

pci-driver 可在crates.io上找到,地址为 https://crates.io/crates/pci-driver。文档在 https://docs.rs/pci-driver

许可证

许可协议为以下之一:

任选其一。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义,您有意提交的任何贡献,均应如上所述双重许可,不附加任何额外条款或条件。


lib.rs:

用于开发用户空间PCI和PCIe驱动程序的crate。

驱动程序开发接口围绕表示PCI 功能PciDevice trait展开,它允许您

  1. 访问其配置空间;
  2. 访问由其基址寄存器(BARs)定义的区域;
  3. 访问其扩展ROM;
  4. 将其DMA操作控制的IOMMU的映射添加和删除;
  5. 配置其INTx、MSI和MSI-X中断向量;
  6. 重置它。

此trait的实现称为后端。目前提供了一个单一的 VfioPciDevice 后端,它依赖于Linux的VFIO驱动程序框架。此后端的可选性可以通过 vfio crate功能控制。未来的后端将各自有一个对应的功能。请注意,用户无法从外部此crate实现额外的后端。

此crate需要Rust 1.56或更高版本。

以下部分展示了 PciDevice 的功能。

配置空间

调用 PciDevice::config 返回一个 PciConfig 值,该值提供对设备配置空间的访问。配置空间由8位、16位和32位寄存器组成。

每个寄存器可能代表一个单一的数值(例如,“厂商ID”)或是一个位域。位域由几个独立的位(例如,“命令”)或位序列组成。在某些情况下,相关的寄存器会组织成层次结构分组(例如,“类代码”)。(这里使用的术语可能与PCI/PCIe规范中的术语不完全一致。)

此库提供对每个寄存器、位域、位序列和位的“结构化”访问,使用专门的访问器方法,因此您不需要记住寄存器的偏移量、位操作的掩码和移位等细节。

尽管如此,如果您真的想要,您可以绕过所有这些,直接在配置空间的任意偏移量处读取和写入。

API还简化了遍历能力和扩展能力,以及查找具有特定能力ID的能力,同时提供上述描述的相同类型的结构化访问接口。

示例用法

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();

BAR和扩展ROM

可以使用 PciDevice::bar 方法获取与设备给定的基本地址寄存器(BAR)对应的 OwningPciRegion。此值的行为类似于 PciConfig 的实例,但不提供上述“结构化访问”功能,因为BAR内容是设备特定的。此外,OwningPciRegion 提供了将区域映射到进程内存的能力(如果该区域可映射)。

还提供了一个类似的 PciDevice::rom 方法,提供对设备“扩展ROM”的访问。

示例用法

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)?;

请参阅以下内容以了解如何轻松创建结构化访问API,您可以使用它来访问具有特定布局的BAR和其他区域:pci_struct!pci_bit_field!

IOMMU

PciDevice::iommu 方法返回一个 PciIommu 值,该值可用于操作影响设备的IOMMU映射。

示例用法

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) };

中断

PciDevice::interrupts 方法返回一个 PciInterrupts 值,该值提供了对设备中断向量的控制。它允许您将特定的中断向量与eventfd描述符相关联,以及取消该关联。

示例用法

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()?;

VFIO后端特定性

以下示例中,设备0000:00:01.0和0000:00:02.0属于VFIO组42,设备0000:00:03.0属于组123。

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()?;

pci_struct!pci_bit_field!

许多时候,您的设备BAR或ROM将类似于配置空间组织成寄存器和位域,或者此库不提供结构化访问API的能力,甚至厂商特定能力中的内容也有一些结构,这是此库自然无法捕获的。

在C语言中,您通常会将指针转换为覆盖您想要访问的区域的相应内存上的 struct,然后使用可变访问。在Rust中,您也可以做类似的事情,但这是不安全的,仅在BAR或ROM被内存映射时才有效,并且由于填充可能导致错误地构建结构。

一个安全的替代方案是拥有 read()write() 函数,这些函数接受一个偏移量和长度,或者通过您想要读取或写入的类型进行参数化。这个crate通过 PciRegion trait提供了这种访问API,但使用它可能很麻烦,并且很容易传递错误的偏移量或读取/写入错误的数据类型。当你正在位操作来设置标志和应用于写入掩码以保留某些位时,事情变得更糟。

为了解决这个问题,这个crate提供了 pci_struct!pci_bit_field! 宏,您可以使用这些宏轻松定义语义感知类型,这些类型提供对设备区域和位字段寄存器的结构化访问。这些也被crate本身用来定义像 PciConfigPciStatus 这样的类型。

PciClassCode 为例

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>,
    }
}

此类型的值可以使用 PciClassCode::backed_by(subregion) 创建,其中 subregion 是实现了 AsPciSubregion<'a> 的任何内容,并且结构从该子区域的开始处开始。

每个字段都遵循格式 name @ offset : type,并产生一个具有给定 name 的方法,该方法返回一个给定 type 的值。该 offset 是从结构开始处的字节数。

PciConfig 的定义是另一个好例子

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 ...
    }
}

注意其中一个字段实际上是我们上面定义的类型:PciClassCode。我们还为它指定了一个偏移量,这将作为它包含的字段的基偏移量。

还要注意 "命令" 和 "状态" 字段。这些是 位字段。下面是如何定义 PciStatus

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,
    }
}

此类型的值可以使用与使用 pci_struct! 定义的类型完全相同的方式创建:使用 PciStatus::backed_by(subregion)。位字段被认为是给定子区域的开始。

PciStatus 的定义也遵循与使用 pci_struct! 相同的总体方案,但现在每一行代表寄存器中的位或位集合。首先,注意结构名称后面的 : RW u16:这意味着该结构表示一个16位宽的读/写寄存器。

然后,我们在位置0处有“立即就绪”位,即寄存器中的最低位。它是只读的,因此是RO。每个位的格式是name @ bit : mode,而对于超过1位的集合,格式是name @ first_bit--last_bit : modefirst_bitlast_bit是包含的)。

然后我们有一个模式为RsvdZ__行,表示2个连续的位。术语RsvdZ来自PCI/PCIe规范,意味着当整体写入寄存器时,这些位必须始终写入为0。还有一个RsvdP模式,表示受影响的位必须按照当前读取的方式精确写入。(这两个模式存在是为了向前兼容的目的)。

向下几行,我们到达“主数据奇偶校验错误”位,其模式为RW1C。这意味着位可以像往常一样读取,并且可以清除(即设置为0),但不能设置(设置为1),因此位不是完全的读写。(RW1C再次来自PCI/PCIe规范,大致代表读或写1以清除。)还有普通的RW位,可以自由读取、清除和设置,但在这个示例中没有展示。

最后,让我们看看“DEVSEL 时序”,它占用位9和10,模式为RO u8。这是一组只能读取不能写入的两个位,读取时以u8返回(它也可以是u16u32)。

在这些所有情况下,字段的名称产生一个方法,该方法返回一个值,允许您检查(可能操纵)位或位集。(请注意,对于RsvdZRsvdP位,字段的name被忽略,但它必须在那里。它不能是单个_,因为这不是一个标识符,所以我们使用__代替。)

您不需要覆盖寄存器中的每个位,尽管我们在上面的示例中这样做了。未指定的位等同于指定为RsvdP

最后,请注意,当使用pci_struct!pci_bit_field!时,您可以为结构体或位字段类型本身以及它们的每个字段或位添加文档注释。

依赖关系

~165KB