PCIE的mmio内存映射访问机制

本文深入探讨了PCI总线与PCIe总线的区别,阐述了PCIe端到端连接方式及内存映射访问机制,包括mmap函数在Linux环境下的应用,以及如何通过mmap访问PCIe设备的配置空间。文章还详细解释了如何利用mmap函数将内存空间映射到内核空间,并提供了实例代码解析PCIe设备配置空间的各个关键字段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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~7bitcapbility ID,如果他的值是10h,就表明这是个PCIe设备的cap structure,也就是说我们已经找到了PCIe设备啦,如果他不是10h怎么办?没关系,因为在00h7~15bit的地方还有next Cap pointer,跟Capbility structure原理类似,他也是个看起来像指针的字节,里面存放的就是下一个capbility structure的地址了,我们只需要根据这里的地址把基地址偏移过去就好了,直到我们找到next cap pointer0的时候,就代表他后面已经没有了,这时候我们就要跳出当前循环了



下面说明一下我们需要找到并打印出来的东西(Capbility structure)

1.Type02h4~7bit,根据这个位置,将基地址偏移即可!





2.Speed:0x2ch1~7bit,注意是1~7而不是0~7 





3.Link Speed:0x0ch0~3bit 





4.Link Width:0x0ch4~9bit,寄存器图同上0x0ch


<span style="font-family:Microsoft YaHei;font-size:14px;">#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;
}</span>

回想题目其实挺简单,不过我当时真的做了好久……

 

### PCIe MMIO 的定义 PCIe MMIO(Memory-Mapped I/O)是一种在计算机系统中用于实现处理器与I/O设备之间通信的技术。在MMIO机制下,I/O设备的寄存器和内部存储被映射到处理器的地址空间中,使得处理器可以直接通过读写内存地址来访问和控制这些I/O设备。这种机制简化了硬件访问的复杂性,并提高了数据传输的效率。[^2] ### PCIe MMIO 的工作原理 在传统的I/O地址空间访问模式中,所有的I/O设备都需要通过特定的I/O指令进行访问,这种方式不仅效率低下,而且限制了系统的扩展性。MMIO通过将I/O设备的寄存器映射到统一的内存地址空间,使得处理器可以像访问普通内存一样访问I/O设备[^2]。 在PCIe系统中,MMIO被广泛采用,尤其是在新开发的硬件和软件中。PCIe设备的配置空间、寄存器以及本地内存都可以通过MMIO机制进行访问。主机处理器通过PCIe地址转换机制,将MMIO地址映射到设备的物理地址,从而实现对设备的直接读写操作。[^1] MMIO的工作流程通常包括以下步骤: 1. **地址映射**:在系统初始化阶段,操作系统或固件会为每个PCIe设备分配一段内存地址空间,这段地址空间与设备的寄存器或内存区域相对应。 2. **访问操作**:当处理器需要访问某个I/O设备时,它会直接读写对应的内存地址。这些内存访问操作会被硬件自动转换为PCIe事务,最终传递到目标设备。 3. **数据传输**:设备接收到读写请求后,执行相应的操作,例如读取寄存器状态或写入新的配置值。 ### MMIO 的优势 MMIO相比传统的I/O地址空间访问方式具有以下优势: - **简化编程模型**:由于I/O设备被映射到内存地址空间,开发者可以使用标准的内存访问指令进行设备操作,而不需要使用特殊的I/O指令。 - **提高性能**:MMIO可以利用处理器的缓存机制,从而提高访问效率。 - **支持更大的地址空间**:MMIO可以支持更大的地址范围,允许访问更多的设备资源。 ### 示例代码 以下是一个简单的C语言示例,展示了如何通过`mmap`函数将MMIO区域映射到用户空间,并进行寄存器读写操作: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #define MMIO_BASE 0xFE000000 // 示例MMIO基地址 #define MMIO_SIZE 0x1000 // 映射大小 int main() { int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { perror("open"); return -1; } void *mmio = mmap(NULL, MMIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMIO_BASE); if (mmio == MAP_FAILED) { perror("mmap"); close(fd); return -1; } // 读取寄存器值 uint32_t reg_val = *(volatile uint32_t *)(mmio + 0x10); // 偏移量0x10 printf("Register value: 0x%x\n", reg_val); // 写入新的寄存器值 *(volatile uint32_t *)(mmio + 0x10) = 0x12345678; // 清理资源 munmap(mmio, MMIO_SIZE); close(fd); return 0; } ``` ### 注意事项 在使用MMIO时需要注意以下几点: - **地址对齐**:访问MMIO寄存器时,必须确保数据宽度对齐,例如32位寄存器应使用32位访问[^4]。 - **内存保护**:在用户空间访问MMIO时,通常需要通过`/dev/mem`设备文件,并确保程序具有足够的权限。 - **缓存一致性**:某些系统可能需要手动管理缓存一致性,以确保读写操作的正确性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值