目录
在 PCIe 链路上发送和接收数据涉及到多个层次的协议和硬件配置。PCIe 是一种高速串行总线标准,用于连接主机和外设。以下是 PCIe 链路上发送和接收数据的基本步骤和方法:
1. 硬件准备
确保 PCIe 设备和主机之间的物理连接正确:
- PCIe 卡插入插槽:确保 PCIe 卡牢固插入主板上的插槽中。
- 电源连接:确保所有外部电源连接器(如 6-pin 或 8-pin 电源连接器)都已正确连接。
- BIOS 设置:确保 BIOS 设置正确,特别是 PCIe 链路宽度和速度设置。
2. 初始化 PCIe 链路
确保 PCIe 链路已经成功初始化并建立连接:
- 物理层初始化:参考之前的章节,确保物理层初始化完成。
- 链路训练:确保 LTSSM(Link Training and Status State Machine)已经完成链路训练,链路处于 L0 状态。
3. 配置 PCIe 设备
确保 PCIe 设备的配置空间正确配置:
- 配置空间访问:使用配置空间访问机制(如配置读写命令)配置 PCIe 设备。
- BAR 设置:设置 Base Address Registers (BARs),指定设备的内存映射地址。
4. 数据传输协议
PCIe 使用 TLP(Transaction Layer Packets)进行数据传输。以下是发送和接收数据的基本步骤:
发送数据
-
创建 TLP:
- 创建一个 TLP,包含必要的头部信息和数据负载。
- 头部信息包括 TLP 类型(如内存读写、IO 读写、配置读写等)、请求者 ID、标签等。
-
发送 TLP:
- 将 TLP 发送到 PCIe 链路上。
- 使用 DMA(Direct Memory Access)控制器将数据从主机内存传输到 PCIe 设备。
-
处理完成:
- 接收方处理 TLP 并返回完成 TLP(Completion TLP)。
- 发送方等待完成 TLP 以确认数据传输成功。
接收数据
-
接收 TLP:
- 监听 PCIe 链路,接收传入的 TLP。
- 解析 TLP 头部信息,确定 TLP 类型和数据负载。
-
处理 TLP:
- 根据 TLP 类型处理数据。
- 对于内存写 TLP,将数据写入指定的内存地址。
- 对于内存读 TLP,从指定的内存地址读取数据并返回读取结果。
-
返回完成 TLP:
- 创建并发送完成 TLP,确认数据传输成功。
- 完成 TLP 包含必要的头部信息和状态代码。
5. 示例代码
以下是一个简单的示例,展示如何在 Linux 环境下使用 PCIe 设备发送和接收数据。
发送数据
c
深色版本
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/pci.h>
#define BAR0_SIZE 0x1000
int main() {
int fd;
void *bar0;
unsigned long bar0_addr;
unsigned int data = 0x12345678;
// 打开设备文件
fd = open("/dev/pci-device", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// 获取 BAR0 地址
if (ioctl(fd, PCIIOC_BASE, &bar0_addr) < 0) {
perror("ioctl");
close(fd);
return 1;
}
// 映射 BAR0 到用户空间
bar0 = mmap(NULL, BAR0_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bar0_addr);
if (bar0 == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 发送数据到 BAR0
*(unsigned int *)bar0 = data;
// 清理
munmap(bar0, BAR0_SIZE);
close(fd);
return 0;
}
接收数据
c
深色版本
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/pci.h>
#define BAR0_SIZE 0x1000
int main() {
int fd;
void *bar0;
unsigned long bar0_addr;
unsigned int data;
// 打开设备文件
fd = open("/dev/pci-device", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// 获取 BAR0 地址
if (ioctl(fd, PCIIOC_BASE, &bar0_addr) < 0) {
perror("ioctl");
close(fd);
return 1;
}
// 映射 BAR0 到用户空间
bar0 = mmap(NULL, BAR0_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bar0_addr);
if (bar0 == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 从 BAR0 读取数据
data = *(unsigned int *)bar0;
printf("Received data: 0x%08X\n", data);
// 清理
munmap(bar0, BAR0_SIZE);
close(fd);
return 0;
}
6. 使用 PCIe 驱动程序
对于复杂的 PCIe 设备,通常会编写专门的驱动程序来管理数据传输。以下是一个简单的 PCIe 驱动程序示例:
驱动程序示例
c
深色版本
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/io.h>
static struct pci_device_id pci_device_ids[] = {
{ PCI_DEVICE(0x1234, 0x5678) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, pci_device_ids);
static int __init my_pci_driver_init(void) {
struct pci_dev *pdev;
resource_size_t bar0_addr;
void __iomem *bar0;
pdev = pci_get_device(PCI_VENDOR_ID_MY, PCI_DEVICE_ID_MY, NULL);
if (!pdev) {
pr_err("PCI device not found\n");
return -ENODEV;
}
if (pci_enable_device(pdev)) {
pr_err("Failed to enable PCI device\n");
return -ENODEV;
}
bar0_addr = pci_resource_start(pdev, 0);
bar0 = ioremap(bar0_addr, pci_resource_len(pdev, 0));
if (!bar0) {
pr_err("Failed to map BAR0\n");
pci_disable_device(pdev);
return -ENOMEM;
}
// 发送数据
iowrite32(0x12345678, bar0);
// 接收数据
pr_info("Received data: 0x%08X\n", ioread32(bar0));
iounmap(bar0);
pci_disable_device(pdev);
return 0;
}
static void __exit my_pci_driver_exit(void) {
pr_info("Driver exiting\n");
}
module_init(my_pci_driver_init);
module_exit(my_pci_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple PCIe Driver Example");
总结
在 PCIe 链路上发送和接收数据涉及多个步骤,包括硬件准备、链路初始化、设备配置、数据传输协议和驱动程序开发。通过这些步骤,可以确保 PCIe 设备之间的高效和可靠的数据传输。以下是一个简化的流程:
- 硬件准备:确保 PCIe 设备和主机之间的物理连接正确。
- 初始化 PCIe 链路:确保链路训练完成,链路处于 L0 状态。
- 配置 PCIe 设备:设置 BARs 和其他配置参数。
- 发送数据:创建 TLP 并通过 PCIe 链路发送。
- 接收数据:监听 PCIe 链路,接收并处理 TLP。
- 使用驱动程序:编写和加载 PCIe 驱动程序,管理数据传输。
通过这些步骤,可以有效地在 PCIe 链路上发送和接收数据。