NVMe开发——PCIe配置空间和地址空间

2024-03-04 20:20

本文主要是介绍NVMe开发——PCIe配置空间和地址空间,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


1. 配置空间

PCI通过引入标准化的配置寄存器和能力结构,提供了一种统一的方式来分配、配置和管理系统资源。PCIe的配置空间在PCI的基础上做了扩展,支持更多功能。

1.1. PCI兼容配置空间

PCI的配置空间为256字节,PCIe兼容此结构。PCI只用前面64字节,剩余的192字节,PCIe扩展了如下功能:

  • PCI Express Capability(PCI Express能力)
  • Power Management(电源管理)
  • MSI and/or MSI-X(MSI和/或MSI-X)

而配置空间,主要关注配置头,因为PCIe设备有两种,一种是终端设备,一种是转换器设备。下文主要关注PCIe终端设备。Vendor ID、Device ID、Revision ID、Subsystem Vendor ID、Subsystem Device ID、Base Class Code、Sub Class Code等字段。

  • Vendor ID:厂商标识。是由PCI SIG授权分配的一个ID。
  • Device ID:设备标识。这个是Pcie 规范给客户自定义的一个ID,用于区分对同款主控(如marvell、SMI)进行2次开发的不同厂商。
  • Revision ID:芯片版本;
  • Subsystem Vendor ID:子系统厂商标识;
  • Subsystem Device ID:子系统设备标识;
  • Base Class Code:设备类型的编码,比如01h表示mass storage controller;
  • Sub Class Code:设备子类型编码,比如08h表示ssd controller。

1.2. PCIe扩展配置空间

PCI的256字节信息不够PCIe使用,所以PCIe在此基础上将配置空间扩展到4K字节,如下图中列出了一些主要的扩展寄存器。

1.3. 配置读写机制

1.3.1. 传统PCI机制

传统PCI机制是直接IO访问,针对X86处理器,这种方式只能访问64K IO地址空间。CPU提供了1字节、2字节和4字节这3种IO大小读写IO端口的接口。

  1. 配置访问的端口地址

在CPU的0xCF8处理配置端口地址,端口地址定义如下:

  • Bit[31]配置1表示使能访问配置空间
  • Bits[7:2]的值表示对应的PCI兼容配置空间的64个DWORD。
  1. 读取配置地址的数据

配置完访问端口的地址之后,就可以读取配置空间的数据。通过0xCFC的CPU地址来直接读取。

1.3.2. 增强配置访问机制

传统的访问方式分为两步,这在多核访问时,可能存在竞态的情况,加锁处理影响性能。另外传统方式只能访问256字节,这也限制了对PCIe扩展空间的访问。为此,提出了一个新的增强配置访问机制,即将每个功能的配置空间映射到设备的256MB空间中,按4K对齐来分配访问。

U64 phyAddr = baseAddr + bus<<20 + device<<15 + function<<12;

baseAddr来自ACPI的GFCM值

1.4. 操作示例

Linux下的PCIe设备信息都直接映射成了文件,如可以直接读取/sys/bus/pci/devices/0000:01:00.0/config文件即是配置空间信息。以下示例主要针对Windows系统,并且使用WinIO驱动来实现。

1.4.1. 直接端口访问

SetPortVal和GetPortVal均为WinIO的接口。

int GetPCIConfiguation(int bus, int dev, int fun, PCI_COMMON_CONFIG &pci_config)
{DWORD addr, data;addr = 1<<31 | (bus << 16) | (dev << 11) | (fun << 8);if (!SetPortVal(0xcf8, addr, 4)){return -1;}if (!GetPortVal(0xcfc, &data, 4)){return -2;}// 当0号寄存器的内容为0xffffffff时,即表明无设备if (i == 0 && data == 0xffffffff){return 0;}memcpy(((PUCHAR)&pci_config) + i, &data, 4);return 1;
}

1.4.2. 内存映射

    DWORD bufferSize = GetSystemFirmwareTable('ACPI', 0, nullptr, 0);if (bufferSize == 0) {return 1;}BYTE* pBuffer = new BYTE[bufferSize]();DWORD result = GetSystemFirmwareTable('ACPI', 'GFCM', pBuffer, bufferSize);__int64 baseAddr = *(__int64 *)(pBuffer + 44);int GetPCIConfiguation(int bus, int dev, int fun, PCI_COMMON_CONFIG& pci_config)
{PDWORD pdwLinAddr;tagPhysStruct PhysStruct;PhysStruct.dwPhysMemSizeInBytes = 256;// baseAddr为基地址PhysStruct.pvPhysAddress = baseAddr | (bus << 20) | (dev << 15) | (fun << 12);pdwLinAddr = (PDWORD)MapPhysToLin(PhysStruct);if (pdwLinAddr == NULL){return -1;}else if (*pdwLinAddr == 0xffffffff){return 0;}// 这种拷贝方式可能拷贝出奇奇怪怪的值,可以使用上面的方法memcpy(((PUCHAR)&pci_config), pdwLinAddr, 256);UnmapPhysicalMemory(PhysStruct);return 1;
}

2. 设备枚举

2.1. 结构

PCIe系统所有设备构成一个拓扑结构,由Root Complex(相当于Host Controller),P2P(桥接设备)和 EndPoint Device(终端设备)构成。可以根据此信息来进行下一级设备的枚举。如下图:

2.2. P2P

每个桥设备(P2P)包含有最后一级总线号(Subordinate Bus Number)、下一级总线号(Secondary Bus Number)和上一级总线号(Primary Bus Number)等信息。然后我们就可以从RC的Pri/Sec/Sub信号获取其下一级的设备信息,然后通过深度优化来遍历整个设备树。

2.3. 设备类型

如何判断设备是Type0还是Type1呢?Header Type寄存器描述了相关信息。

  • Bits[6:0]描述设备类型
    • 0表示设备为终端设备
    • 1表示设备为P2P(PCI-to-PCI)设备,连接2个Bus。
    • 2表示卡总线桥接设备,传统接口,现在不常用。
  • Bit[7]表示是否为多功能设备,0表示单功能设备,1表示多功能设备。

2.4. 注意项

  1. 设备不存在时,RC设备向系统响应的是全1(FFFFH)的数据。
  2. 设备未准备好,针对低速设备,至少在复位后100ms之后再来枚举设备,针对高速设备如速率大于5.0 GT/s(Gen3速度),则软件必须等到链路训练完成后的100ms才能尝试这样做。这是因为速度越高,越需要更多的时候来完成链路的训练。
  3. 枚举时,通过判断 Vendor ID为非FFFF来确定设备存在。

2.5. Secondary Bus

Hot Reset需要通过Secondary Bus Reset来控制,所以获取获取指定设备的Secondary Bus。除了通过遍历整个拓扑结构图来查询设备的次级总线外,Linux下还可以直接获取设备路径来更方便地查看次级总线BDF地址。


root@ubuntu22:~# readlink /sys/bus/pci/devices/0000:0f:00.0
../../../devices/pci0000:00/0000:00:02.2/0000:0f:00.0
# 0000:00:02.2即为次级总线,0000表示PCIe总线逻辑域,表示为RC0。

2.6. 示例

以下示例为Linux下的深度优先搜索。

__int64 BaseAddr()
{FILE* file = fopen("/sys/firmware/acpi/tables/MCFG", "rb");if (file == NULL) {perror("Failed to open MCFG table file");return 0;}// 获取文件大小fseek(file, 0, SEEK_END);long fileSize = ftell(file);rewind(file);// 分配缓冲区uint8_t* buffer = (uint8_t*)malloc(fileSize);if (buffer == NULL) {perror("Failed to allocate memory for buffer");fclose(file);return 0;}// 读取文件内容size_t bytesRead = fread(buffer, 1, fileSize, file);if (bytesRead != fileSize) {perror("Failed to read MCFG table file"); free(buffer);fclose(file);return 0;}fclose(file);__int64 baseAddr = *(__int64 *)(buffer + 44);return baseAddr;
}// 深度优先搜索PCI设备
void DFSPCI(uint8_t bus, int fd, uint64_t baseAddr, int indentationNum)
{uint64_t addr;uint8_t* ptrdata;uint8_t type;for (int dev = 0; dev < 32; dev++){for (int fun = 0; fun < 8; fun++){addr = baseAddr | (bus<<20) | (dev<<15) | (fun<<12);//要寻找的偏移地址,根据PCIe的物理内存偏移ptrdata = (uint8_t *)mmap(NULL, 64, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr);//映射后返回的首地址 ---物理地址if (ptrdata == NULL){printf("mmap函数映射失败.\n");munmap (ptrdata, 64);close(fd);return;}else if (*ptrdata == 0xff || *ptrdata == 0){munmap (ptrdata, 64);if (fun == 0)break;elsecontinue;}type = *(ptrdata + 0xe) & 0x01;for (int i = 0; i < indentationNum; i++) {printf("|   ");}printf("+--- %02x:%02x.%x\n", bus, dev, fun);if (type == 1){uint8_t subBus = *(ptrdata + 0x19);DFSPCI(subBus, fd, baseAddr, indentationNum + 1);}munmap (ptrdata, 64);}}
}

3. Capabilities

Capabilities是一组描述PCIe设备功能相关的结构,它是一个链式的结构,一个指向下一个,直到最后。

3.1. Capability Pointer

下图中的Capability Pointer的值指向第1个Capability在配置空间中的偏移。

3.2. Power Management Capability

如果Capability ID为01h,则表明当前结构为电源管理能力寄存器结构。

3.3. MSI Capability

3.4. MSI-X Capability

3.5. PCIe Capability

PCIe Capability结构不同偏移对应不同的功能,详细如下图:

3.6. 示例

  1. 偏移0x34上的0x80即为Capabilities Pointer.
  2. 偏移0x80上的0x10表示此处存储的是PCIe Capability,Next Pointer偏移为0xd0。
  3. 偏移0xd0上的0x11表示此处存储的是MSI-X Capability,Next Pointer偏移为0xe0。
  4. 偏移0xe0上的0x05表示此处存储的是MSI Capability,Next Pointer偏移为0xf8。
  5. 偏移0xf8上的0x01表示此处存储的是Power Management Capability,Next Pointer为0表示结束。

4. 地址空间

在早期的PC时期,CPU提供接口直接通过IO地址空间来访问IO设备,然而因为多CPU访问时导致竞态的限制。目前系统更多是将IO地址空间直接映射到系统的物理地址空间中(MMIO,Memory-mapped I/O),这样软件可以像访问普通内存一样访问IO设备。

4.1. P-MMIO和NP-MMIO

在PCIe中,可预取(Prefetchable)和非可预取(Non-Prefetchable)内存空间之间的区别在于对于读取操作是否具有副作用以及是否允许写合并。

可预取空间的特点包括:

  • 读取操作没有副作用:从可预取空间读取数据不会改变目标设备的状态信息。
  • 允许写合并:当写入多个连续地址时,可预取空间可以将这些写操作合并为更少的传输请求,提高性能。

而非可预取空间则不具备上述特点。

在可预取空间中,数据可能会被提前获取,因为预测到请求方可能会在不久的将来需要更多的数据。这种缓存数据的预取可以提高性能,因为当真正需要数据时,它已经位于缓存中。然而,如果请求方实际上并未使用额外的数据,那么缓存中的数据最终会被丢弃以释放缓冲区空间。由于读取操作本身没有副作用,因此在稍后重新获取原始数据是可行的。

由于PCIe的优势和先进性,通常更倾向于使用可预取内存空间来获得更好的性能。

4.2. BAR

PCIe支持不同功能的设备,不同的设备其需要操作的内存大小也不同,为了更方便地实现这一种,BAR(Base Address Registers)产生了。PCIe可以通过配置空间中的BAR来为不同的设备配备不同的内存方案。NVMe存储设备是Type0,并且一般只用BAR0-1或BAR4-5。

4.3. BAR配置

BAR有32位和64位地址两种,现在基本都是64位系统,此处只讲64位地址。即BAR0-1存储的是一个64位的物理地址。

  1. 向BAR Pair写全1,然后读出来。如果读出来的值为1,表明此地址位是可以操作的,为0表明不可操作。如上图(2)中,最低可操作位为26位,即2的26次方64K,即BAR Pair的最小操作单位为64K。最高操作们来64位,上限非常大。
  2. 如图(3)中,通过BAR Pair给设备配置0x2,4000,0000的物理内存地址,并配置64位地址、可预取的IO请求。

这篇关于NVMe开发——PCIe配置空间和地址空间的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/774332

相关文章

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

wolfSSL参数设置或配置项解释

1. wolfCrypt Only 解释:wolfCrypt是一个开源的、轻量级的、可移植的加密库,支持多种加密算法和协议。选择“wolfCrypt Only”意味着系统或应用将仅使用wolfCrypt库进行加密操作,而不依赖其他加密库。 2. DTLS Support 解释:DTLS(Datagram Transport Layer Security)是一种基于UDP的安全协议,提供类似于

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/