PCIe獲取BAR空間長度

參考文章:

 

本文的重點就是“如何獲取BAR空間長度”,在此之前,先鋪墊一些基礎。

基本原理:

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

其中Type0 Header最多有6個BAR,而Type1 Header最多有兩個BAR。這就意味着,對於Endpoint來說,最多可以擁有6個不同的地址空間。但是實際應用中基本上不會用到6個,通常1~3個BAR比較常見。

主要注意的是,如果某個設備的BAR沒有被全部使用,則對應的BAR應被硬件全被設置爲0,並且告知軟件這些BAR是不可以操作的。對於被使用的BAR來說,其部分低比特位是不可以被軟件操作的,只有其高比特位纔可以被軟件操作。而這些不可操作的低比特決定了當前BAR支持的操作類型和可申請的地址空間的大小。

 

《PCIe 體系結構導讀》2.3.2 (10)中提到,當PCI設備復位後,BAR寄存器將用於存放初始化信息,包括 IO/存儲器空間、32bit/64bit地址解碼、支持可預讀/不可預讀、BAR空間的長度等等信息,那如何獲取這些信息呢?

其實,在BAR寄存器[3:0]屬性字段用於記錄這些信息,軟件讀取BAR的值,其中操作的類型一般由最低四位所決定,具體如下圖所示。

 

通過BAR寄存器[3:0]屬性字段可以獲取BAR的操作類型等信息,那麼BAR空間長度又要如何獲取呢?

如圖中(1)所示,未初始化的BAR的低比特(11~4)都是0,高比特(31~12)都是不確定的值。所謂初始化,就是系統(軟件)向整個BAR都寫1,來確定BAR的可操作的最低位是哪一位。當前可操作的最低位爲12,因此當前BAR可申請的(最小)地址空間大小爲4KB(2^12)。如果可操作的最低位爲20,則該BAR可申請的(最小)地址空間大小爲1MB(2^20)。

下面是一個申請64MB P-MMIO地址空間的例子,由於採用的是64-bit的地址,因此需要兩個BAR。具體如下圖所示:

注:需要特別注意的是,軟件對BAR的檢測與操作(Evaluating)必須是順序執行的,即先BAR0,然後BAR1,……,直到BAR5。當軟件檢測到那些被硬件設置爲全0的BAR,則認爲這個BAR沒有被使用。

注:無論是PCI還是PCIe,都沒有明確規定,第一個使用的BAR必須是BAR0。事實上,只要設計者原意,完全可以將BAR4作爲第一個BAR,並將BAR0~BAR3都設置爲不使用。

 

代碼分析:

本文的重點就是“如何獲取BAR空間長度”,代碼的重點是“__pci_read_base”函數,在此之前,先介紹一下PCI總線枚舉時,如何調用到該函數。

在PCI Agent設備進行數據傳送之前,系統軟件需要初始化PCI Agent設備的BAR0~5寄存器。系統軟件使用DFS算法對PCI總線進行遍歷時,完成這些寄存器的初始化,即分配這些設備在PCI總線域的地址空間。當這些寄存器初始化完畢後,PCI設備可以使用PCI總線地址進行數據傳遞。

pci_scan_child_bus  //PCI總線樹枚舉,分配PCI總線樹的PCI總線號
    |——pci_scan_slot  //掃描當前PCI總線所有設備,加入設備隊列
        |——pci_scan_device  //對PCI設備的配置寄存器進行讀寫操作
            |——pci_setup_device  //判斷PCI設備類型
                |——pci_read_irq  //獲取Interrupt pin和Line,賦值到irq參數
                |——pci_read_bases  //訪問PCI設備的BAR空間和ROM空間
                    |——__pci_read_base  //初始化resource參數

接下來單獨講解 __pci_read_base函數,僅介紹該函數獲取BAR空間長度的辦法。

int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
		    struct resource *res, unsigned int pos)
{
    mask = type ? PCI_ROM_ADDRESS_MASK : ~0;
    ...
    pci_read_config_dword(dev, pos, &l);
	pci_write_config_dword(dev, pos, l | mask);
	pci_read_config_dword(dev, pos, &sz);
	pci_write_config_dword(dev, pos, l);
 
 	if (sz == 0xffffffff)  //如果BAR上每一位都能設置意味其不能正常工作
      sz = 0;
    ...
    if (res->flags & IORESOURCE_MEM_64) {
        	pci_read_config_dword(dev, pos + 4, &l);
		pci_write_config_dword(dev, pos + 4, ~0);
		pci_read_config_dword(dev, pos + 4, &sz);
		pci_write_config_dword(dev, pos + 4, l);
  
  		l64 |= ((u64)l << 32);
		sz64 |= ((u64)sz << 32);
		mask64 |= ((u64)~0 << 32);
    }
    sz64 = pci_size(l64, sz64, mask64);  //計算並獲取BAR空間長度
    ...
    region.start = l64;
	 region.end = l64 + sz64;
    ...
}

其中,pci_size函數中,相應操作如下:

static u64 pci_size(u64 base, u64 maxbase, u64 mask)
{
	u64 size = mask & maxbase;	/* Find the significant bits */
	if (!size)
		return 0;

	/* Get the lowest of them to find the decode size, and
	   from that the extent.  */
	size = (size & ~(size-1)) - 1;

	/* base == maxbase can be valid only if the BAR has
	   already been programmed with all 1s.  */
	if (base == maxbase && ((base | size) & mask) != mask)
		return 0;

	return size;
}

pci_size函數中最重要的便是這個公式:size = (size & ~(size-1)) - 1;

其中,size就是之前讀到的 sz64&mask64。

最後通過pci_size函數返回值,就能得到BAR空間長度。

 

這個BAR空間長度有什麼作用?

得到size值後,可以用處初始化 pci_dev->resource的start和end參數。

pci_resource_len函數就是用於記錄BAR的空間長度。

通過pci_resource_start函數獲取BAR起始地址,再加上pci_resource_len得到BAR空間長度,就能計算出當前BAR的有效範圍。

 

實例分析:

以Hi3536爲例,看下BAR空間大小可以如何配置。

一般而言,BAR地址空間申請大小,是有EP設備默認設置,RC側無法修改(除非EP支持bar resize capabilty,需要軟件支持)。

當前PCIE 各個BAR提供靈活的BAR_MASK寄存器(地址是PCIE CFG Base address+0x1000+0x10+N*4),和BAR寄存器[3:0]屬性字段(是否可預取,是否是32位還是64位地址,是否是IO屬性還是MEM屬性)一起配合使用,可以達到擴展64位bar,調整bar mask大小的目的。

        bar mask[0]是使能當前bar;

bar mask[31:1]是MASK大小。

注:如果當前bar是1/3/5,需要bar0/2/4配合一起使用64位bar地址,則不能使能。

 

例如,將當前bar0擴展爲64 位可預取存儲器地址,地址空間擴大爲64M byte;則需要使用到bar1,其修改辦法如下:

步驟 1:EP本地軟件設置bar0 mask爲0x3FF_FFFF;bar0[0]表示使能當前bar0,其bar mask[25:1]爲全1,則當前bar0的[25:4]都不可操作,bar0的[31:26]可以由HOST修改。(即:當前可操作的最低位爲26,因此當前BAR可申請的(最小)地址空間大小爲64MB(2^26)

步驟 2:EP本地軟件設置bar1 mask爲0x0;表示不使能當前bar1,其bar mask[31:1]爲全0,則當前bar1 所有地址都可以由HOST修改。

步驟 3:EP本地軟件設置bar0爲0xC;表示申請的是64 位可預取存儲器地址

步驟 4:HOST 掃描EP。

----結束

Hi3536 uboot下PCIe config代碼如下:

int pcie_conf(void)
{
    ...
    /*memory space enable*/
	__raw_writel(0x2, HISI3536_PCIE_CONFIG_BASE + CFG_COMMAND_REG);

	__raw_writel(0xc, HISI3536_PCIE_CONFIG_BASE + CFG_BAR0_REG);
	__raw_writel(0x0, HISI3536_PCIE_CONFIG_BASE + CFG_BAR1_REG);
	__raw_writel(0xc, HISI3536_PCIE_CONFIG_BASE + CFG_BAR2_REG);
	__raw_writel(0x0, HISI3536_PCIE_CONFIG_BASE + CFG_BAR3_REG);
	__raw_writel(0x0, HISI3536_PCIE_CONFIG_BASE + CFG_BAR4_REG);
	__raw_writel(0x0, HISI3536_PCIE_CONFIG_BASE + CFG_BAR5_REG);

	__raw_writel(0x03ffffff, HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 0);
	__raw_writel(0x0       , HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 1);
	__raw_writel(0x03ffffff, HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 2);
	__raw_writel(0x0       , HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 3);
	__raw_writel(0x0       , HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 4);
	__raw_writel(0x0       , HISI3536_PCIE_CONFIG_BASE + 0x1000 + 0x10 + 4 * 5);

}

代碼分析:

Hi3536中使用了兩個64bit的BAR地址,因此需要將BAR0和BAR1、BAR2和BAR3組合。

配置方法如上述例子。

BAR4和BAR5沒有使用,因此將BAR寄存器和BAR_MASK寄存器置零。

Ps:當軟件檢測到那些被硬件設置爲全0的BAR,則認爲這個BAR沒有被使用。

 

能否將例子帶入到“__pci_read_base”函數中進行分析呢?

我們以Hi3536的BAR0和BAR1爲例,帶入到函數中。

BAR0寄存器值:0x0000000c,BAR0_MASK:0x03ffffff

BAR1寄存器值:0x00000000,BAR1_MAK:0x00000000

int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
		    struct resource *res, unsigned int pos)
{
    /* 
     * enum pci_bar_type {
	 * pci_bar_unknown,	/* Standard PCI BAR probe */
	 * pci_bar_io,		/* An io port BAR */
	 * pci_bar_mem32,		/* A 32-bit memory BAR */
	 * pci_bar_mem64,		/* A 64-bit memory BAR */
     * };
     */
    mask = type ? PCI_ROM_ADDRESS_MASK : ~0;  //type當前的BAR類型爲mem64
    ...
    pci_read_config_dword(dev, pos, &l);  //讀取 l = 0xc
	pci_write_config_dword(dev, pos, l | mask);  //寫入 mask = 0xffffffff
	pci_read_config_dword(dev, pos, &sz);  //讀取 sz = 0xfc00000f
	pci_write_config_dword(dev, pos, l);  //寫入0xc,寄存器恢復初值
 
 	if (sz == 0xffffffff)  //如果BAR上每一位都能設置意味其不能正常工作
      sz = 0;
      
    if (type == pci_bar_unknown) {
        ...
    } else{
        l64 = l & PCI_BASE_ADDRESS_MEM_MASK;  //PCI_BASE_ADDRESS_MEM_MASK = (~0x0fUL)
        sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;  
        mask64 = (u32)PCI_BASE_ADDRESS_MEM_MASK;  
    }}
    
    ...
    if (res->flags & IORESOURCE_MEM_64) {
        	pci_read_config_dword(dev, pos + 4, &l);  //讀取 l = 0x0
		pci_write_config_dword(dev, pos + 4, ~0);  //寫入 0xffffffff
		pci_read_config_dword(dev, pos + 4, &sz);  //讀取 sz = 0xffffffff
		pci_write_config_dword(dev, pos + 4, l);  //寫入0x0
  
  		l64 |= ((u64)l << 32);      //l64 = 0x0
		sz64 |= ((u64)sz << 32);    //sz64 = 0xffffffff_fc000000
		mask64 |= ((u64)~0 << 32);  //mask64 = 0xffffffff_ffffffff
    }
    //l64 = 0x0
    //sz64 = 0xffffffff_fc000000
    //mask64 = 0xffffffff_ffffffff
    sz64 = pci_size(l64, sz64, mask64);  //計算並獲取BAR空間長度
    ...
}

帶入到pci_size函數中:

static u64 pci_size(u64 base, u64 maxbase, u64 mask)
{
	u64 size = mask & maxbase;  //size = 0xffffffff_fc000000
	if (!size)
		return 0;

	/* Get the lowest of them to find the decode size, and
	   from that the extent.  */
	size = (size & ~(size-1)) - 1;
     //(size-1) = 0xffffffff_fbffffff
     //~(size-1) = 0x00000000_04000000
     //(size & ~(size-1)) = 0xffffffff_fc000000 & 0x00000000_04000000 = 0x00000000_04000000
     //size = (size & ~(size-1)) - 1 = 0x00000000_03ffffff
     //計算得出size = 0x00000000_03ffffff
     //BAR空間長度64MB
    
	/* base == maxbase can be valid only if the BAR has
	   already been programmed with all 1s.  */
	if (base == maxbase && ((base | size) & mask) != mask)
		return 0;

	return size;
}

最終得到size值爲64MB。

 

代碼分析完,直接實操設備來試試看:

Hi3536對應PCIe配置空間基址:0x1f000000

hisilicon # md 0x1f000000
1f000000: 353619e5 00100146 04800001 00000000    ..65F...........
1f000010: 0000000c 00000000 0000000c 00000000    ................
1f000020: 00000000 00000000 00000000 00020000    ................
1f000030: 00000000 00000040 00000000 000001ff    ....@...........
1f000040: 5fc35001 00000008 00000000 00000000    .P._............

配置BAR0和BAR1,先寄存器全寫1:

hisilicon # mw 0x1f000010 0xffffffff
hisilicon # mw 0x1f000014 0xffffffff
hisilicon # md 0x1f000000
1f000000: 353619e5 00100146 04800001 00000000    ..65F...........
1f000010: fc00000f ffffff0f 0000000c 00000000    ................
1f000020: 00000000 00000000 00000000 00020000    ................
1f000030: 00000000 00000040 00000000 000001ff    ....@...........
1f000040: 5fc35001 00000008 00000000 00000000    .P._............

即,size64=0xffffff0f_fc00000f,mask64=0xffffffff_ffffffff

帶入pci_size函數,如下:

	size = (size & ~(size-1)) - 1;
     //(size-1) = 0xffffff0f_fbffffff
     //~(size-1) = 0x000000f0_04000000
     //(size & ~(size-1)) = 0xffffff0f_fc000000 & 0x000000f0_04000000 = 0x00000000_04000000
     //size = (size & ~(size-1)) - 1 = 0x00000000_03ffffff
     //計算得出size = 0x00000000_03ffffff
     //BAR空間長度64MB

返回size64值:0x00000000_03ffffff,BAR空間大小爲64M。

Ps:雖然不清楚爲什麼BAR1全寫1的時候,讀出來的值是0xffffff0f,有點偏離,但結果符合預期。

具體原因可能需要詢問海思FAE。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章