Windows操作系统: PCIE Axidma篇 1 BAR扫盲

简介: 设备在系统的PCI地址空间里申请一段来用,所申请的空间基址和大小保存在BAR寄存器里。BAR里的只是PCI域的地址空间,需要映射到IO地址空间里或者内存地址空间里之后软件才能使用。映射到IO空间的话,用IO读写指令和函数去访问设备;映射到内存空间的话,首先得到的是物理地址,映射到虚拟地址后就可以像用指针那样访问。IO BAR和MEM BAR分别是映射到IO空间和内存空间的BAR;BAR寄存器的0位指示要映射到哪,有的设备这位可以由用户设置,有的只读。

BAR:

 也就是基地址寄存器(Base address register):

 设备在系统的PCI地址空间里申请一段来用,所申请的空间基址和大小保存在BAR寄存器里。BAR里的只是PCI域的地址空间,需要映射到IO地址空间里或者内存地址空间里之后软件才能使用。映射到IO空间的话,用IO读写指令和函数去访问设备;映射到内存空间的话,首先得到的是物理地址,映射到虚拟地址后就可以像用指针那样访问。IO BAR和MEM BAR分别是映射到IO空间和内存空间的BAR;BAR寄存器的0位指示要映射到哪,有的设备这位可以由用户设置,有的只读。

 Xilinx的官方论坛上有人是这样解释的感觉很恰当:

 BAR 仅用于地址解码。它没有实现内存,只有访问内存的地址窗口。对于每个 BAR,系统将读取所需内存窗口的大小并分配一个物理地址范围以访问该窗口。在用户端的 FPGA 内,您通常会收到指示哪个 BAR 处于活动状态的信号(哪个地址窗口用于此事务)。然后,您需要解码此信息以决定如何处理传输。9d19b12ae76b4a6d9f6b58e36c354a7a.png

 系统的讲解参考自:

 http://blog.chinaaet.com/justlxy/p/5100053320

 基地址寄存器(BAR)在配置空间(Configuration Space)中的位置如下图所示:

f881d70e2f36404bbf6bbab0a86a0cbd.png

 其中Type0 Header最多有6个BAR,而Type1 Header最多有两个BAR。这就意味着,对于Endpoint来说,最多可以拥有6个不同的地址空间。但是实际应用中基本上不会用到6个,通常1~3个BAR比较常见。

 主要注意的是,如果某个设备的BAR没有被全部使用,则对应的BAR应被硬件全被设置为0,并且告知软件这些BAR是不可以操作的。对于被使用的BAR来说,其部分低比特位是不可以被软件操作的,只有其高比特位才可以被软件操作。而这些不可操作的低比特决定了当前BAR支持的操作类型和可申请的地址空间的大小。

 一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求。

 下面用几个简单的例子来熟悉BAR的机制:

 例1. 32-bit Memory Address Space Request

 如下图所示,请求一个4KB的NP-MMIO一般需要以下三个步骤:

e2dbf2f5b21540539ac85a08c0cf5078.png

 Step1:如图中(1)所示,未初始化的BAR的低比特(114)都是0,高比特(3112)都是不确定的值。所谓初始化,就是系统(软件)向整个BAR都写1,来确定BAR的可操作的最低位是哪一位。当前可操作的最低位为12,因此当前BAR可申请的(最小)地址空间大小为4KB(212)。如果可操作的最低位为20,则该BAR可申请的(最小)地址空间大小为1MB(220)。

 Step2:完成初始化(写1操作)之后,软件便开始读取BAR的值,来确定每一个BAR对应的地址空间大小和类型。其中操作的类型一般由最低四位所决定,具体如上图右侧部分所示。

 Step3:最后一步是,软件向BAR的高比特写入地址空间的起始地址(Start Address)。如图中所示,为0xF9000000。

 例2. 64-bit Memory Address Space Request

 下面是一个申请64MB P-MMIO地址空间的例子,由于采用的是64-bit的地址,因此需要两个BAR。具体如下图所示:

3416ca022cca4093b11f51f71f46e242.png

 例3. IO Address Space Request

 下面是一个申请IO地址空间的例子,如下图所示:

bdabac5ff02b4a31b92895a4402e168b.png

 注:需要特别注意的是,软件对BAR的检测与操作(Evaluating)必须是顺序执行的,即先BAR0,然后BAR1,……,直到BAR5。当软件检测到那些被硬件设置为全0的BAR,则认为这个BAR没有被使用。

 注:无论是PCI还是PCIe,都没有明确规定,第一个使用的BAR必须是BAR0。事实上,只要设计者原意,完全可以将BAR4作为第一个BAR,并将BAR0~BAR3都设置为不使用。

 代码里对bar的读写示例如下:

#include <iostream>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <strsafe.h>
#include <Windows.h>
#include <SetupAPI.h>
#include <INITGUID.H>
#include <WinIoCtl.h>
#include <cstring>
#include <string>
#include <thread>
#include <vector>
#include <Windows.h>
#include <SetupAPI.h>
#include <signal.h>
#include <INITGUID.H>
#include<process.h>
HANDLE get_ctr_bar_handle(const char *path)
{
    HANDLE tmp_handle = NULL,result = NULL;
    tmp_handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (tmp_handle == INVALID_HANDLE_VALUE)
    {
        LOG(LOG_TAG"opening device %s failed,errno = %ld\r\n",path,GetLastError());
        return NULL;
    }
    return tmp_handle;
}
int write_bar(HANDLE dev_handel, off_t target, DWORD value)
{
    DWORD t_value = value;
    DWORD write_size = sizeof(DWORD);
    LARGE_INTEGER offset;
    offset.QuadPart = target;
    if (INVALID_SET_FILE_POINTER == SetFilePointerEx(dev_handel, offset, NULL, FILE_BEGIN))
    {
        LOGE(LOG_TAG"SetFilePointerEx failed,line %d,errno =  %ld\r\n", __LINE__, GetLastError());
        return -(int)GetLastError();
    }
    if (!WriteFile(dev_handel, &t_value, write_size, &write_size, NULL))
    {
        LOGE(LOG_TAG"WriteFile failed,line %d,errno =  %ld\n",__LINE__, GetLastError());
        return -(int)GetLastError();
    }
    //LOGD("write bar:---- address:0x%x,value 0x%x\r\n",target,value);
    return 0;
}
DWORD read_bar(HANDLE dev_handel, off_t target, DWORD *value_ptr)
{
    DWORD read_size = sizeof(DWORD);
    LARGE_INTEGER offset;
    offset.QuadPart = target;
    if (INVALID_SET_FILE_POINTER == SetFilePointerEx(dev_handel, offset, NULL, FILE_BEGIN))
    {
        LOGE(LOG_TAG"SetFilePointerEx failed,line %d,errno = %ld\r\n", __LINE__, GetLastError());
        return -(int)GetLastError();
    }
    if (!ReadFile(dev_handel, value_ptr, read_size, &read_size, NULL))
    {
        LOGE(LOG_TAG"ReadFile failed,line %d,errno = %ld\n", __LINE__, GetLastError());
        return -(int)GetLastError();
    }
    //LOGD("read bar:---- address:0x%x,value 0x%x\r\n", target, *value_ptr);
    return 0;
}


相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
2月前
|
存储 弹性计算 运维
阿里云国际Windows操作系统迁移教程
阿里云国际Windows操作系统迁移教程
|
2月前
|
Unix Linux iOS开发
【换行符】Windows、Unix、Mac不同操作系统的回车符\r和换行符\n
【换行符】Windows、Unix、Mac不同操作系统的回车符\r和换行符\n
|
3月前
|
Windows
Windows操作系统部署安装Kerberos客户端
详细介绍了在Windows操作系统上部署安装Kerberos客户端的完整过程,包括下载安装包、安装步骤、自定义安装路径、修改环境变量、配置hosts文件和Kerberos配置文件,以及安装后的验证步骤。
396 3
Windows操作系统部署安装Kerberos客户端
|
5月前
|
IDE Java 开发工具
如何在Windows操作系统上安装PyCharm?
【7月更文挑战第5天】如何在Windows操作系统上安装PyCharm?
201 59
|
4月前
|
Windows
Windows操作系统中环境变量的检索顺序
Windows操作系统中环境变量的检索顺序
115 3
|
4月前
|
Kubernetes Cloud Native 开发者
探索云原生技术:Kubernetes入门与实践探索Windows操作系统的隐藏功能
【8月更文挑战第31天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性、效率和可靠性的关键。本文将带你了解云原生的核心组件之一——Kubernetes(K8s),通过浅显易懂的语言和实际代码示例,引导你步入这一强大工具的世界。无论你是初学者还是有经验的开发者,本篇都将为你打开一扇通向高效资源管理与自动化部署的大门。
|
4月前
|
负载均衡 网络协议 安全
【Azure 应用服务】Azure Web App的服务(基于Windows 操作系统部署)在被安全漏洞扫描时发现了TCP timestamps漏洞
【Azure 应用服务】Azure Web App的服务(基于Windows 操作系统部署)在被安全漏洞扫描时发现了TCP timestamps漏洞
|
4月前
|
网络协议 应用服务中间件 nginx
性能提升-如何设置Windows操作系统TIME_WAIT状态的TCP连接快速回收时间?
性能提升-如何设置Windows操作系统TIME_WAIT状态的TCP连接快速回收时间?
122 0
|
5月前
|
Windows 内存技术
nvm 管理和切换 node版本(windows操作系统)
nvm 管理和切换 node版本(windows操作系统)
85 0
|
22天前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
42 0
Vanilla OS:下一代安全 Linux 发行版