本文的重点就是“如何获取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 = d = 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, andfrom that the extent. */size = (size & ~(size-1)) - 1;/* base == maxbase can be valid only if the BAR hasalready 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类型为pci_read_config_dword(dev, pos, &l); //读取 l = 0xcpci_write_config_dword(dev, pos, l | mask); //写入 mask = 0xffffffffpci_read_config_dword(dev, pos, &sz); //读取 sz = 0xfc00000fpci_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 = 0x0pci_write_config_dword(dev, pos + 4, ~0); //写入 0xffffffffpci_read_config_dword(dev, pos + 4, &sz); //读取 sz = 0xffffffffpci_write_config_dword(dev, pos + 4, l); //写入0x0l64 |= ((u64)l << 32); //l64 = 0x0sz64 |= ((u64)sz << 32); //sz64 = 0xffffffff_fc000000mask64 |= ((u64)~0 << 32); //mask64 = 0xffffffff_ffffffff}//l64 = 0x0//sz64 = 0xffffffff_fc000000//mask64 = 0xffffffff_ffffffffsz64 = pci_size(l64, sz64, mask64); //计算并获取BAR空间长度...
}
带入到pci_size函数中:
static u64 pci_size(u64 base, u64 maxbase, u64 mask)
{u64 size = mask & maxbase; //size = 0xffffffff_fc000000if (!size)return 0;/* Get the lowest of them to find the decode size, andfrom 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 hasalready 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 ..
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 ..
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。
本文发布于:2024-01-29 00:38:58,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170645994411462.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |