tag 标签: BAR

相关博文
  • 热度 3
    2019-8-14 11:35
    7096 次阅读|
    0 个评论
    如何获取PCIe  BAR
    从网上找到一段话:“pc上电初始化的时候,BIOS接管pc的初始化工作,分配管理内存,分配管理io空间, 所以开始会扫描主板上的pci设备,并且为扫描到的pci设备分配对应的内存或者io,分配完毕之后,把BAR 写入, 所以bar是有bios分配的,不是用户设定,bar是基地址,是物理内存地址,而如何获得pci对应的 bar的内存大小上面有说,向pci的bar写入 0xffffffff,之后又读取bar,读到的值清空低3bit然后去取 反,加1,就是内存大小。” 正如上图所示,2K大小的BAR空间值显示的是h'FFFFF800,所以可以根据上述方法,得到其实际大小就是h'800,即2K。 这好像是和PCI的规范有关,BAR空间大小(或者地址)由掩码方式表示,上述2k其实是从4G(FFFFFFFF)到4.294965248G(FFFFF800)的这段空间。
  • 热度 36
    2019-8-8 15:19
    4082 次阅读|
    0 个评论
    创建PCIe空间映射子函数
    BAR初始化第一步,即BAR_SCAN子函数了解完了,下面我们了解初始化第二步,即TSK_BUILD_PCIE_MAP函数。再次复习下BAR初始化步骤( 在TSK_BAT_INIT任务里面其实是在模拟BIOS和驱动程序的操作,首先会对BAR寄存器进行赋值,然后会对BAR指向的设备内存进行IO或者mem映射,最后就是对设备的配置寄存器的参数写入。 ): /************************************************************ Task : TSK_BAR_INIT Inputs : None Outputs : None Description : Initialize PCI core based on core's configuration. *************************************************************/ task TSK_BAR_INIT; begin TSK_BAR_SCAN; TSK_BUILD_PCIE_MAP; TSK_DISPLAY_PCIE_MAP; TSK_BAR_PROGRAM; end endtask // TSK_BAR_INIT TSK_BUILD_PCIE_MAP源代码有点长,这里就不贴了。其主要目的就是执行存储器或I/O映射算法,并依据Endpoint需求分配Memory 32、Memory 64和I/O空间。具体来说,就是根据上一步 TSK_BAR_SCAN得到的各个BAR的range变量,检查这些range,根据检查结果,判断每个BAR是否被使能、及其对应的映射结果(是MEM32啊还是MEM64啊,或者是I/O空间)。仿真的时候会将检查结果打印如下: 注 :函数 TSK_BUILD_PCIE_MAP在检查正常的时候只会打印上图中的第一句;下面每个BAR空间的检查结果,是在调用函数TSK_DISPLAY_PCIE_MAP的时候打印的。 BAR空间初始化最后一步,看似就是将之前读回的BAR状态重新使用Type0配置写再写回去( 这里应该对应应用程序里的BAR空间基地址的产生,电脑开机扫描获取PCIe的各个BAR的信息,为它们分配地址空间,这里的Program应该是将分配好的地址空间基地址重新写回到BAR寄存器 ),比如对BAR0的写: // Program BAR0 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h10, BAR_INIT_P_BAR , 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); 这个子函数还对其它BAR空间进行Type0配置写,个人觉得,其它BAR空间都Disable了,写不写都无所谓。值得注意的时候,该子函数最后还对PCI命令寄存器和PCIe器件控制寄存器进行了编程: // Program PCI Command Register TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h04, 32'h00000003, 4'h1); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Program PCIe Device Control Register TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h68, 32'h0000005f, 4'h1); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(1000); 注意上述两次写和BAR0写的区别,比如最后一个参数为4‘h1,则表示只有最低字节被Enable了,即只需传递低8位即可。 了解完BAR空间的初始化,后面开始了解正常的PIO仿真,即存储器写和存储器读。
  • 热度 4
    2019-8-8 14:46
    3066 次阅读|
    0 个评论
    Type0配置写函数
    在前面了解PCI配置空间、TLP以及Type0配置读的基础上,本文介绍Type0配置写子函数。该子函数产生一个Type0配置写TLP,先来看具体代码: /************************************************************ Task : TSK_TX_TYPE0_CONFIGURATION_WRITE Inputs : Tag, PCI/PCI-Express Reg Address, First BypeEn Outputs : Transaction Tx Interface Signaling Description : Generates a Type 0 Configuration Write TLP *************************************************************/ task TSK_TX_TYPE0_CONFIGURATION_WRITE; input tag_; input reg_addr_; input reg_data_; input first_dw_be_; begin if (trn_lnk_up_n) begin $display(" : Trn interface is MIA", $realtime); $finish(1); end TSK_TX_SYNCHRONIZE(0, 0, 0); trn_td <= #(Tcq) { 1'b0, 2'b10, 5'b00100, 1'b0, 3'b000, 4'b0000, 1'b0, 1'b0, 2'b00, 2'b00, 10'b0000000001, // 32 COMPLETER_ID_CFG, tag_, 4'b0000, first_dw_be_, // 64 COMPLETER_ID_CFG, 4'b0000, reg_addr_ , 2'b00, // 32 reg_data_ , reg_data_ , reg_data_ , reg_data_ // 64 }; trn_tsof_n <= #(Tcq) 0; trn_teof_n <= #(Tcq) 0; trn_trem_n <= #(Tcq) 2'b00; trn_tsrc_rdy_n <= #(Tcq) 0 ; TSK_TX_SYNCHRONIZE(1, 1, 1); trn_tsof_n <= #(Tcq) 1; trn_teof_n <= #(Tcq) 1; trn_trem_n <= #(Tcq) 2'b00; trn_tsrc_rdy_n <= #(Tcq) 1; end endtask // TSK_TX_TYPE0_CONFIGURATION_WRITE 有了对Type0配置读的了解,那么理解上面的代码就容易很多了,TLP前面32bit和配置读一样,唯一的区别在于TLP最后32bit带了1DW的写数据。写数据通过函数第三个参数调用的时候引入。 需要注意的是,上述代码中两次调用了函数 TSK_TX_SYNCHRONIZE,第一次调用( TSK_TX_SYNCHRONIZE(0, 0, 0); ),只是为了同步trn_clk和trn_tdst_rdyn_n,之后TLP信息被赋值给trn_td。第二次调用( TSK_TX_SYNCHRONIZE(1, 1, 1); )是为了同步信号,也是为了将TLP信息添加到本地buffer,并最终发送到输出log。也就是说,我们在仿真的时候看到很多下图所示的信息都是第二次调用函数 TSK_TX_SYNCHRONIZE的时候处理,第二次调用除了在仿真的时候的打印输出下图所示的信息外,还将TLP信息Log到输出文件(tx.dat和rx.dat) 这里有个疑问是在TSK_BAR_SCAN函数中,对每个BAR先使用Type配置写,写入的数据是”P_ADDRESS_MASK = 32'hffff_ffff;“,源代码给出的注释是对BAR空间写PCI_MASK来找到range。接着使用Type0配置读刚刚被写入MASK值的BAR空间,读回的数据保存在BAR_INIT_P_BAR_RANGE 里,也就是所谓“ 找到range ”。我的问题是,为什么对BAR空间写入MASK后,再对其进行type0读就可以得到range?
  • 热度 26
    2019-8-8 12:59
    3686 次阅读|
    0 个评论
    前面博文有介绍在仿真测试功能代码里前面几步分别是设置仿真时间(以防止仿真被无休止挂起)、系统初始化(等待复位被释放以及链路链接ok)、BAR空间初始化等等。其中在BAR初始化子函数里调用了本文要介绍的BAR空间扫描子程序,其具体代码是: /************************************************************ Task : TSK_BAR_SCAN Inputs : None Outputs : None Description : Scans PCI core's configuration registers. *************************************************************/ task TSK_BAR_SCAN; begin //-------------------------------------------------------------------------- // Write PCI_MASK to bar's space via PCIe fabric interface to find range //-------------------------------------------------------------------------- P_ADDRESS_MASK = 32'hffff_ffff; DEFAULT_TAG = 0; DEFAULT_TC = 0; $display(" : Inspecting Core Configuration Space...", $realtime); // Determine Range for BAR0 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h10, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR0 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h10, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for BAR1 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h14, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR1 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h14, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for BAR2 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h18, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR2 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h18, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for BAR3 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h1C, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR3 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h1C, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for BAR4 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h20, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR4 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h20, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for BAR5 TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h24, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read BAR5 Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h24, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; // Determine Range for Expansion ROM BAR TSK_TX_TYPE0_CONFIGURATION_WRITE(DEFAULT_TAG, 12'h30, P_ADDRESS_MASK, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_TX_CLK_EAT(100); // Read Expansion ROM BAR Range TSK_TX_TYPE0_CONFIGURATION_READ(DEFAULT_TAG, 12'h30, 4'hF); DEFAULT_TAG = DEFAULT_TAG + 1; TSK_WAIT_FOR_READ_DATA; BAR_INIT_P_BAR_RANGE = P_READ_DATA; end endtask // TSK_BAR_SCAN 查看代码,该子程序主要对几个BAR空间执行了一系列PCI Type0配置读和写,以此确定并获取Endpoint的每个BAR空间的存储器和IO操作要求。获取的信息存储在全局阵列变量 BAR_INIT_P_BAR_RANGE 之中。注意这个函数只能在系统初始化函数之后被调用。 代码中Type0读写配置函数的第二个参数对应于每个BAR的基地址,比如BAR0对应h'10。
  • 热度 4
    2019-8-8 12:46
    5694 次阅读|
    0 个评论
    学习PCI配置空间(TSK_TX_TYPE0_CONFIGURATION_READ)
    PCI总线定义了两类配置请求,一个是Type00h配置请求,另一个是Type 01h配置请求。 其中HOST主桥或者PCI桥使用Type 00h配置请求,访问与HOST主桥或者PCI桥直接相连的PCI Agent设备或者PCI桥;而使用Type 01h配置请求,需要至少穿越一个PCI桥,访问没有与其直接相连的PCI Agent设备或者PCI桥。在PCI总线中,只有PCI桥能够接收Type 01h配置请求。Type 01h配置请求不能直接发向最终的PCI Agent设备,而只能由PCI桥将其转换为Type 01h继续发向其他PCI桥,或者转换为Type 00h配置请求发向PCI Agent设备。 我们在看ursapp_tx代码的时候,发现有很多地方用的Type0配置读和写这两个子程序,后面会给出详细代码。对于配置空间里的基本配置空间需要详细了解。在PG54的45页有详细的说明,下图就是截取于PG054: 注意上图中BAR0到BAR5的地址,分别是从h'10、h'14、h'18、h'1C、h'20、h'24.。下面给出Type0配置读子程序详细代码,注意代码中trn_td的位宽是128bit,在64bit模式中,需要两次赋值,每次64bit,总共128bit。 /************************************************************ Task : TSK_TX_TYPE0_CONFIGURATION_READ Inputs : Tag, PCI/PCI-Express Reg Address, First BypeEn Outputs : Transaction Tx Interface Signaling Description : Generates a Type 0 Configuration Read TLP *************************************************************/ task TSK_TX_TYPE0_CONFIGURATION_READ; input tag_; input reg_addr_; input first_dw_be_; begin if (trn_lnk_up_n) begin $display(" : Trn interface is MIA", $realtime); $finish(1); end TSK_TX_SYNCHRONIZE(0, 0, 0); trn_td <= #(Tcq) { 1'b0,//对应TLP头中第一个字节的R 2'b00,//对应TLP头中的FMT,这里是7系器件,通用定义R和FMT合并在一处 5'b00100,//对应TLP头中的Type 1'b0,//对应TLP头第二个字节里的R 3'b000,//对应TLP头中的TC 4'b0000,//对应TLP头中的R+Attr2+R+TH 1'b0,//对应TLP头中的TD 1'b0,//对应TLP头中的EP 2'b00,//对应TLP头中的Attr 2'b00,//对应TLP头中的AT 10'b0000000001, // 32,对应TLP头中的Length COMPLETER_ID_CFG, tag_, 4'b0000, first_dw_be_, // 64 COMPLETER_ID_CFG, 4'b0000, reg_addr_ , 2'b00, 32'b0 }; trn_tsof_n <= #(Tcq) 0; trn_teof_n <= #(Tcq) 0; trn_trem_n <= #(Tcq) 2'b01; trn_tsrc_rdy_n <= #(Tcq) 0 ; TSK_TX_SYNCHRONIZE(1, 1, 1); trn_tsof_n <= #(Tcq) 1; trn_teof_n <= #(Tcq) 1; trn_trem_n <= #(Tcq) 2'b00; trn_tsrc_rdy_n <= #(Tcq) 1; end endtask // TSK_TX_TYPE0_CONFIGURATION_READ 为了更好地理解上述代码,我们还需要了解一个概念,即 TLP(Transaction Layer Packet) 。 当处理器或者其他PCIe设备访问PCIe设备时,所传送的数据报文首先通过事务层被封装为一个或者多个TLP,之后才能通过PCIe总线的各个层次发送出去。 一个完整的TLP由1个或者多个TLP Prefix(由协议引入,FPGA开发可以不关注)、TLP头、Data Payload(数据有效负载)和TLP Digest组成。TLP头是TLP最重要的标志,不同的TLP其头的定义并不相同。TLP头包含了当前TLP的总线事务类型、路由信息等一系列信息。在一个TLP中,Data Payload的长度可变,最小为0,最大为1024DW。 TLPDigest是一个可选项,一个TLP是否需要TLP Digest由TLP头决定。DataPayload也是一个可选项,有些TLP并不需要DataPayload,如存储器读请求、配置和I/O写完成TLP并不需要Data Payload。 TLP头由3个或者4个双字(DW)组成。其中第一个双字中保存通用TLP头,其他字段与通用TLP头的Type字段相关。一个通用TLP头由Fmt、Type、TC、Length等字段组成,如下图(截取PG054第46页,这是典型32位地址存储器写请求TLP格式)所示。需要注意的是,上述代码的格式和下图的不太一样,除了第一个DW外,其它2个或3个DW都与具体的Type类型有关。 如果存储器读写TLP支持64位地址模式时,TLP头的长度为4DW,否则为3DW。而完成报文的TLP头不含有地址信息,使用的TLP头长度为3DW。 FMT=“00” 表示 TLP大小为3个双字,不带数据。 “01” 表示 TLP大小为4个双字,不带数据。 “10” 表示 TLP大小为3个双字,带数据。 “11” 表示 TLP大小为4个双字,带数据。 FMT和Type详细解码如下: 下面介绍Length字段的意义,如此对应TLP基本就有个大概认识了: 在存储器读请求TLP中并不包含Data Payload,在该报文中,Length字段表示需要从目标设备数据区域读取的数据长度;而在存储器写TLP中,Length字段表示当前报文的DataPayload长度。Length字段的最小单位为DW。当该字段为n时,表示需要获得的数据长度或者当前报文的数据长度为n个DW,其中0代表0x3FF。值得注意的是,当n等于0时,表示数据长度为1024个DW。 上述代码中Length字段是1,读取某个BAR空间状态信息,将返回一个DW的信息。在TSK_BAR_SCAN子函数里,使用Type0配置读,扫描各个BAR并将扫描得到的信息存储在全局变量中,以备后续使用。 关于PCIe的基础知识,这里有篇文章,介绍的非常好:https://blog.csdn.net/cllovexyh/article/details/79828833
相关资源