https://blog.csdn.net/Jmilk/article/details/106007926
打开 dpdk-18.08/drivers/bus/pci/linux/pci.c 可以看到以下内容:
#define PCI_MAX_RESOURCE 6
/*
* PCI 扫描文件系统下的 resource 文件
* @param filename: 通常为 /sys/bus/pci/devices/{pci_addr}/resource 文件
* @param dev[out]: dpdk 中对一个 PCI 设备的抽象
*/
static int
pci_parse_sysfs_resource(const char *filename, struct rte_pci_device *dev)
{
FILE *f;
char buf[BUFSIZ];
int i;
uint64_t phys_addr, end_addr, flags;
f = fopen(filename, "r"); // 先打开 resource 文件,只读
if (f == NULL) {
RTE_LOG(ERR, EAL, "Cannot open sysfs resource\n");
return -1;
}
// 扫描 6 次,因为 PCI 最多有 6 个 BAR
for (i = 0; i<PCI_MAX_RESOURCE; i++) {
if (fgets(buf, sizeof(buf), f) == NULL) {
RTE_LOG(ERR, EAL,
"%s(): cannot read resource\n", __func__);
goto error;
}
// 扫描 resource 文件拿到 BAR
if (pci_parse_one_sysfs_resource(buf, sizeof(buf), &phys_addr,
&end_addr, &flags) < 0)
goto error;
// 如果是 Memory BAR,则进行记录
if (flags & IORESOURCE_MEM) {
dev->mem_resource[i].phys_addr = phys_addr;
dev->mem_resource[i].len = end_addr - phys_addr + 1;
/* not mapped for now */
dev->mem_resource[i].addr = NULL;
}
}
fclose(f);
return 0;
error:
fclose(f);
return -1;
}
/*
* 扫描 PCI resource 文件中的某一行
* @param line: 某一行
* @param len: 长度,为第一个参数字符串的长度
* @param phys_addr[out]: PCI BAR 的起始地址,这个地址要 mmap() 才能用
* @param end_addr[out]: PCI BAR 的结束地址
* @param flags[out]: PCI BAR 的标志
*/
int
pci_parse_one_sysfs_resource(char *line, size_t len, uint64_t *phys_addr,
uint64_t *end_addr, uint64_t *flags)
{
union pci_resource_info {
struct {
char *phys_addr;
char *end_addr;
char *flags;
};
char *ptrs[PCI_RESOURCE_FMT_NVAL];
} res_info;
// 字符串处理
if (rte_strsplit(line, len, res_info.ptrs, 3, ' ') != 3) {
RTE_LOG(ERR, EAL,
"%s(): bad resource format\n", __func__);
return -1;
}
errno = 0;
// 字符串处理,拿到 PCI BAR 起始地址、PCI BAR 结束地址、PCI BAR 标志
*phys_addr = strtoull(res_info.phys_addr, NULL, 16);
*end_addr = strtoull(res_info.end_addr, NULL, 16);
*flags = strtoull(res_info.flags, NULL, 16);
if (errno != 0) {
RTE_LOG(ERR, EAL,
"%s(): bad resource format\n", __func__);
return -1;
}
return 0;
}
- 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
这段代码的逻辑很简单,就是扫描某个 PCI 设备的 resource 文件并获得 Memory BAR。e.g.
$ cat /sys/bus/pci/devices/0000:00:08.0/resource
0x0000000000001000 0x000000000000103f 0x0000000000040101
0x00000000c0040000 0x00000000c0040fff 0x0000000000040200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000440000000 0x0000000440003fff 0x000000000014220c
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000c0000000 0x00000000c003ffff 0x000000000004e200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
前 6 行为 PCI 设备的 6 个 BAR,还是以 Intel 82599 为例,前两个 BAR 为 Memory BAR,中间两个 BAR 为 IO BAR,最后两个 BAR 为 MSI-X BAR。其中,每个 BAR 又分为 3 列:
- 第 1 列为 PCI BAR 的起始地址
- 第 2 列为 PCI BAR 的终止地址
- 第 3 列为 PCI BAR 的标识
再看一段代码 dpdk-18.08/drivers/bus/pci/linux/pci_uio.c:
/*
* 用于映射 resource 资源,并获取 PCI BAR
* @param dev:DPDK 中关于某一个 PCI 设备的抽象实例
* @param res_id:说明要获取第几个 BAR
* @param uio_res:用来存放 PCI BAR 资源的结构
* @param map_idx、uio_res:数组的计数器
*/
int
pci_uio_map_resource_by_index(struct rte_pci_device *dev, int res_idx,
struct mapped_pci_resource *uio_res, int map_idx)
{
..... // 省略
// 打开 /dev/bus/pci/devices/{pci_addr}/resource0..N 文件
if (!wc_activate || fd < 0) {
snprintf(devname, sizeof(devname),
"%s/" PCI_PRI_FMT "/resource%d",
rte_pci_get_sysfs_path(),
loc->domain, loc->bus, loc->devid,
loc->function, res_idx);
/* then try to map resource file */
fd = open(devname, O_RDWR);
if (fd < 0) {
RTE_LOG(ERR, EAL, "Cannot open %s: %s\n",
devname, strerror(errno));
goto error;
}
}
/* try mapping somewhere close to the end of hugepages */
if (pci_map_addr == NULL)
pci_map_addr = pci_find_max_end_va();
// 进行 mmap() 映射,拿到 PCI BAR 在进程虚拟空间下的地址
mapaddr = pci_map_resource(pci_map_addr, fd, 0,
(size_t)dev->mem_resource[res_idx].len, 0);
close(fd);
if (mapaddr == MAP_FAILED)
goto error;
pci_map_addr = RTE_PTR_ADD(mapaddr,
(size_t)dev->mem_resource[res_idx].len);
// 将拿到的 PCI BAR 映射至进程虚拟空间内的地址存起来
maps[map_idx].phaddr = dev->mem_resource[res_idx].phys_addr;
maps[map_idx].size = dev->mem_resource[res_idx].len;
maps[map_idx].addr = mapaddr;
maps[map_idx].offset = 0;
strcpy(maps[map_idx].path, devname);
dev->mem_resource[res_idx].addr = mapaddr;
return 0;
error:
rte_free(maps[map_idx].path);
return -1;
}
/*
* 对 pci/resource0..N 进行 mmap(),将 PCI BAR 空间通过 mmap 的方式映射到进程内部的虚拟空间,供用户态应用来操作设备
*/
void *
pci_map_resource(void *requested_addr, int fd, off_t offset, size_t size,
int additional_flags)
{
void *mapaddr;
// 核心便是这句 mmap,其中要注意的是 offset 必须为 0
mapaddr = mmap(requested_addr, size, PROT_READ | PROT_WRITE,
MAP_SHARED | additional_flags, fd, offset);
if (mapaddr == MAP_FAILED) {
RTE_LOG(ERR, EAL,
"%s(): cannot mmap(%d, %p, 0x%zx, 0x%llx): %s (%p)\n",
__func__, fd, requested_addr, size,
(unsigned long long)offset,
strerror(errno), mapaddr);
} else
RTE_LOG(DEBUG, EAL, " PCI memory mapped at %p\n", mapaddr);
return mapaddr;
}
PCIe概述
PCI总线使用并行总线结构,采用单端并行信号,同一条总线上的所有设备共享总线带宽
PCIe总线使用高速差分总线,采用端到端连接方式,每一条PCIE链路只能连接两个设备
PCIe的端到端连接方式
发送端和接收端都含有TX(发送逻辑),RX(接受逻辑)
现在来说明什么是mmio
mmio,memory map io内存映射访问机制,除了port I/O之外,另外一种访问方式就是mmio了
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<—->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
这里就自然地提到一个函数,
mmap(linux环境)
头文件 <sys/mman.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_toffset);
int munmap(void* start,size_t length);
mmap函数用来将内存空间映射到内核空间
munmap用来解除这个映射关系
函数参数:start映射区開始地址,設置為NULL時表示由系統決定
length映射区長度單位,地址自然是sizeof(unsigned long)
prot設置為PORT_READ|PORT_WRITE表示頁可以被读写
flag指定映射对象类型,MAP_SHARED表示與其他所有映射這個对象的所有进程共享映射空间
fd,/dev/mem文件描述符
offset被映射對象內容的起點
这里还需要提到一个文件 /dev/mem
“/dev/mem”物理內存全映像,可以用來訪問物理內存,一般是open("/dev/mem",O_RD_WR),然后mmap,接著就可以用mmap地址訪問物理內存(root权限)
这是PCIe物理内存地址!
)
遍历时只需要根据相应的offset偏移即可PCIe设备的配置空间!
关于PCIe设备配置空间的0x34位置的Capabilites Pointer,需要说一说
这是0x34h位置即Capability Pointer所代表的空间,也就是说Capability Pointer虽然听起来像一个指针,但是他只是一个8bit的数据,这里面存放了一个地址,而这个地址,就是我们要找的,
意义上Capability Pointer所指向的东西,我们来看看他究竟要指向哪里,就是这里喽!
capbility pointer里面存放的地址就是上图00h的地址喽,也就是上表的基地址,那么问题来了我们为什么要找这个表呢?在这个表中,00h位置0~7bit是capbility ID,如果他的值是10h,就表明这是个PCIe设备的cap structure,也就是说我们已经找到了PCIe设备啦,如果他不是10h怎么办?没关系,因为在00h的7~15bit的地方还有next Cap pointer,跟Capbility structure原理类似,他也是个看起来像指针的字节,里面存放的就是下一个capbility structure的地址了,我们只需要根据这里的地址把基地址偏移过去就好了,直到我们找到next cap pointer为0的时候,就代表他后面已经没有了,这时候我们就要跳出当前循环了
下面说明一下我们需要找到并打印出来的东西(Capbility structure表)
1.Type:02h的4~7bit,根据这个位置,将基地址偏移即可!
2.Speed:0x2ch的1~7bit,注意是1~7而不是0~7
3.Link Speed:0x0ch的0~3bit
4.Link Width:0x0ch的4~9bit,寄存器图同上0x0ch
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<sys/mman.h> //mmap #include<fcntl.h> //open filel #define MAX_BUS 5 //一般来说到4就够了 #define MAX_DEV 32 #define MAX_FUN 8 #define BASE_ADDR 0xf8000000 //我的电脑的基地址,你的可能不一样哦 #define LEN_SIZE sizeof(unsigned long) typedef unsigned int WORD; typedef unsigned char BYTE;//8bit void typeshow(BYTE data) // { printf("%x\n",data); switch(data) { case 0x00:printf("PCIExpress Endpoint device\n"); break; case 0x01:printf("LegacyPCI Express Endpoint device\n"); break; case 0x04:printf("RootPort of PCI Express Root Complex\n"); break; case 0x05:printf("Upstream Port of PCI Express Switch\n"); break; case 0x06:printf("DownstreamPort of PCI Express Switch\n"); break; case 0x07:printf("PCiExpress-to-PCI/PCI-x Bridge\n"); break; case 0x08:printf("PCI/PCI-xto PCi Express Bridge\n"); break; case 0x09:printf("RootComplex Integrated Endpoint Device\n"); break; case 0x0a:printf("RootComplex Event Collector\n"); break; default:printf("reserved\n"); break; } printf("\n"); } void speedshow(BYTE speed) { printf("%x\n",speed); switch(speed) { case 0x00:printf("2.5GT/S"); break; case 0x02:printf("5GT/S"); break; case 0x04:printf("8GT/S"); break; default:printf("reserved"); break; } printf("\n\n"); } void linkspeedshow(BYTE speed) { printf("%x\n",speed); switch(speed) { case 0x01:printf("SupportedLink Speeds Vector filed bit 0"); break; case 0x02:printf("SupportedLink Speeds Vector filed bit 1"); break; case 0x03:printf("SupportedLink Speeds Vector filed bit 2"); break; case 0x04:printf("SupportedLink Speeds Vector filed bit 3"); break; case 0x05:printf("SupportedLink Speeds Vector filed bit 4"); break; case 0x06:printf("SupportedLink Speeds Vector filed bit 5"); break; case 0x07:printf("SupportedLink Speeds Vector filed bit 6"); break; default:printf("reserved"); break; } printf("\n\n"); } void linkwidthshow(BYTE width) { printf("%x\n",width); switch(width) { case 0x01:printf("x1"); break; case 0x02:printf("x2"); break; case 0x04:printf("x4"); break; case 0x08:printf("x8"); break; case 0x0c:printf("x12"); break; case 0x10:printf("x16"); break; case 0x20:printf("x32"); break; default:printf("reserved"); break; } printf("\n\n"); } int main() { WORD addr=0; WORDbus,dev,fun; WORD *ptrdata; WORD*ptrsearch; BYTE nextpoint;//8bit int fd; int i; fd=open("/dev/mem",O_RDWR); //“/dev/mem”物理內存全映像,可以用來訪問物理內存,一般是open("/dev/mem",O_RD_WR),然後mmap,接著就可以用mmap地址訪問物理內存 if(fd<0) { printf("openmemory failed!\n"); return -1; } printf("fd=%d\n",fd); for(bus=0;bus<MAX_BUS;++bus) { for(dev=0;dev<MAX_DEV;++dev) { for(fun=0;fun<MAX_FUN;++fun) { //addr=0; addr=BASE_ADDR|(bus<<20)|(dev<<15)|(fun<<12);//要寻找的偏移地址,根据PCIe的物理内存偏移 ptrdata=mmap(NULL,LEN_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,addr);//映射后返回的首地址 //void*mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset); //start映射區開始地址,設置為NULL時表示由系統決定 //length映射區長度單位,地址自然是sizeof(unsigned long) //prot設置為PORT_READ|PORT_WRITE表示頁可以被讀寫 //flag指定映射對象類型,MAP_SHARED表示與其他所有映射這個對象的所有進程共享映射空間 //fd文件描述符 //offset被映射對象內容的起點 if(ptrdata==(void *)-1) { munmap(ptrdata,LEN_SIZE); break; } if(*ptrdata != 0xffffffff) { nextpoint=(BYTE)(*(ptrdata+0x34/4));//capability pointer ptrsearch=ptrdata + nextpoint/4;//the base address of capability structure printf("next point:%x\n",nextpoint); printf("ptrdata:%x\n",*ptrdata); printf("search:%x\n",*ptrsearch); while(1)//search for the pcie device { if((BYTE)(*ptrsearch)==0x10)//capability id of 10h indicating pcie capabilitystructure { printf("PCIE:"); printf("busnumber=%x,dev number=%x,function number=%x\n",bus,dev,fun); printf("vender id:%x\t",(*ptrdata)&0x0000ffff); printf("deviceid:%x\n",((*ptrdata)>>8)&0x0000ffff); printf("ptrsearch:%x\n",*ptrsearch); printf("type:"); typeshow((BYTE)(((*ptrsearch)>>20)&0x0f)); printf("speed:"); speedshow((BYTE)(((*(ptrsearch+0x2c/4))>>1)&0x7f)); printf("linkspeed:"); linkspeedshow((BYTE)(*(ptrsearch+0x0c/4)&0x0f)); printf("linkwidth:"); linkwidthshow((BYTE)(((*(ptrsearch+0x0c/4))>>4)&0x3f)); printf("***************************"); break;//havefound the pcie device and printed all the message,break the while } if((BYTE)((*ptrsearch)>>8)==0x00)//no pcie device exsist break; ptrsearch=ptrdata+((BYTE)(((*ptrsearch)>>8)&0x00ff))/4;//next cap pointer printf("next pointer:%x\n",*ptrsearch); } } munmap(ptrdata,LEN_SIZE); } } } close(fd); return 0; }