linux-5.10.110内核源码分析 - bcm2711 pcie BAR地址分配
1、pcie地址(bcm2711 Address Maps)
1.1、pcie地址(Address Maps)
1.2、dts(ranges)
pcie0: pcie@7d500000 {
compatible = "brcm,bcm2711-pcie";
reg = <0x0 0x7d500000 0x0 0x9310>;
device_type = "pci";
#address-cells = <3>;
#interrupt-cells = <1>;
#size-cells = <2>;
interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "pcie", "msi";
interrupt-map-mask = <0x0 0x0 0x0 0x7>;
interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 143
IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 2 &gicv2 GIC_SPI 144
IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 3 &gicv2 GIC_SPI 145
IRQ_TYPE_LEVEL_HIGH>,
<0 0 0 4 &gicv2 GIC_SPI 146
IRQ_TYPE_LEVEL_HIGH>;
msi-controller;
msi-parent = <&pcie0>;
ranges = <0x02000000 0x0 0xc0000000 0x6 0x00000000
0x0 0x40000000>;
/*
* The wrapper around the PCIe block has a bug
* preventing it from accessing beyond the first 3GB of
* memory.
*/
dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000
0x0 0xc0000000>;
brcm,enable-ssc;
};
从dts及bcm2711 Address Maps可以看到,pcie的cpu域地址为0x0600000000-0x063fffffff,大小为0x40000000。
2、pcie初始化(brcm_pcie_probe)
2.1、添加资源到pci_host_bridge->windows(pci_add_resource_offset)
对于当前调用栈,pci_add_resource_offset函数的resources参数即为pci_host_bridge->windows,参数res即为dts里面描述的ranges的cpu域地址范围0x0600000000-0x063fffffff以及其他数据,参数offset即为cpu域地址到pci地址的偏移(dts里面cpu域地址-pci域地址)。
2.2、添加资源到pci_bus->resources(pci_bus_add_resource)
2.3、ep配置空间映射(brcm_pcie_map_conf)
配置空间映射(brcm_pcie_map_conf),bcm2711所有设备配置空间的cpu域地址都是一个地址,通过写PCIE_EXT_CFG_INDEX索引寄存器来区分对不同设备配置空间的读写:
2.4、BAR0资源读取计算(__pci_read_base)
__pci_read_base读取计算BAR0的资源:
计算方法参考上图,计算过程大致可以解释为向BAR写全1,其中上图中的4-25位固定为0,写1之后仍然为0,0-3也是固定的,写1保持不不变;
pci_size的:
u64 size = mask & maxbase; /* Find the significant bits */
是将0-3位清0,清0之后,size的高位都为1,低位都为0。
pcie_size的:
size = size & ~(size-1);
“(size-1)”因为size的低位都为0,都需要向前借1,借1减1之后0变成1,也就是上图的0-25位都变成1,因为25位需要向26位借1,所以26位变成0,最终就是26位之外的二进制位都为1,“~(size-1)”取反之后除26位之外为1,其他位都为0,“size & ~(size-1)”最终结果还是第26位为1,也就是BAR大小,2的26次方。
3、BAR资源分配(pci_bus_alloc_resource)
3.1、资源管理方式(resource)
BAR资源管理大致如下所示,已分配的资源链接到child,如果资源没有分配那么child为空,child、sibling之间按地址从小到大链接在一起,child及sibling都是已经分配了的资源,child、sibling及sibling、sibling之间的空隙为没有分配的空闲资源(BAR资源要求对齐,对齐之后BAR资源之间有空隙),child之前及最后一个sibling之后的资源为空闲资源:
3.2、查找空闲资源(__find_resource)
查找空闲资源主要过程就是依次在最前面的空闲资源、已分配的资源节点之间、最后空闲资源里
面查找符合要求的资源区间,然后返回该区间,这里没有分配资源,仅返回空闲区间:
/*
* Find empty slot in the resource tree with the given range and
* alignment constraints
*/
static int __find_resource(struct resource *root, struct resource *old,
struct resource *new,
resource_size_t size,
struct resource_constraint *constraint)
{
struct resource *this = root->child; // 已分配的资源链表
struct resource tmp = *new, avail, alloc;
tmp.start = root->start; // 从root->start开始查找空闲资源
/*
* Skip past an allocated resource that starts at 0, since the assignment
* of this->start - 1 to tmp->end below would cause an underflow.
*/
if (this && this->start == root->start) { // 已经有分配资源,并且root->start已经被分配出去,那么从root->child之后开始查找空闲资源
tmp.start = (this == old) ? old->start : this->end + 1; // old有值为重新分配内存的情况,这里不考虑,从this->end + 1开始查找空闲资源,也就是root->child之后
this = this->sibling; // this指向下一个已分配的资源节点(tmp是介于两个已分配资源节点之间的空闲资源)
}
for(;;) {
if (this)
tmp.end = (this == old) ? this->end : this->start - 1; // tmp的结束地址指向下一个已分配资源的开始地址的前一个地址,空闲资源的结束地址
else
tmp.end = root->end; // tmp之后没有已经分配的资源节点,tmp到资源的最后都是空闲资源
if (tmp.end < tmp.start) // 前后两个节点之间没有空闲资源的情况(两个资源节点的内存紧挨着)
goto next;
resource_clip(&tmp, constraint->min, constraint->max);
arch_remove_reservations(&tmp);
/* Check for overflow after ALIGN() */
avail.start = ALIGN(tmp.start, constraint->align); // 起始地址对齐(对齐之后的地址作为空闲资源的起始地址)
avail.end = tmp.end; // 可用资源的结束地址(tmp.end之后已经被分配了)
avail.flags = new->flags & ~IORESOURCE_UNSET;
if (avail.start >= tmp.start) { // 对齐之后的地址大于等于tmp.start(对齐之后的地址在空闲资源范围内,而不是与前一个已经分配的资源节点的地址重叠冲突)
alloc.flags = avail.flags;
alloc.start = constraint->alignf(constraint->alignf_data, &avail,
size, constraint->align); // 根据pcie硬件参数等,对分配的资源地址进行对齐
alloc.end = alloc.start + size - 1; // 起始地址对齐之后,更新结束地址
if (alloc.start <= alloc.end && // 没看到什么情况会不成立,除非size小于等于0
resource_contains(&avail, &alloc)) { // alloc的地址在可用资源地址范围之内,那么alloc作为查找到的可用资源保存到new,这个时候只是返回一个可用的范围,没有真正申请资源
new->start = alloc.start;
new->end = alloc.end;
return 0;
}
}
next: if (!this || this->end == root->end)
break;
if (this != old) // (对于非重新分配内存,需要更新tmp.start,对于重新分配资源,如果this是旧的节点,那么可以把旧的节点再次分配给新的资源)
tmp.start = this->end + 1; // tmp.start更新为当前sibling结束地址的下一个地址
this = this->sibling; // sibling跟前一个节点之间的空闲资源不满足需要申请的资源,查找当前sibling及下一个sibling之间的空闲资源
}
return -EBUSY;
}
3.3、申请空闲资源(__request_resource)
__request_resource申请空闲资源比较简单,主要就是将前面查找到的空闲资源按起始地址大小插入到pci_bus->resources->child已分配资源链表:
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
resource_size_t start = new->start; // 新申请资源的起始地址
resource_size_t end = new->end; // 新申请资源的结束地址
struct resource *tmp, **p;
if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child; // 已分配资源的第一个节点
for (;;) {
tmp = *p; // root->child或者tmp->sibling
if (!tmp || tmp->start > end) { // 第一次循环,如果tmp为空,那么资源没有分配,new就是第一个分配的资源节点,root->child指向new即可,非第一次循环tmp为空,表示已经查找到已经分配资源节点的最后一个节点的下一个节点,也就是NULL,那么new就是最后一个节点
new->sibling = tmp; // new节点的下一个节点(new是最后一个节点,那么tmp为NULL,如果new不是最后一个节点,那么tmp是new的下一个节点)
*p = new; // 第一次分配内存资源的情况下,root->child = new;非第一次内存分配,new插入p节点后
new->parent = root;
return NULL;
}
p = &tmp->sibling; // 保存前一个节点的sibling,用于记录new的前区节点
if (tmp->end < start)
continue;
return tmp;
}
}
4、写BAR寄存器(pci_std_update_resource)
4.1、cpu域转pci域地址(pcibios_resource_to_bus)
资源里面分配的地址是cpu域地址,BAR里面保存的是pci域地址,需要找到资源在哪个resource里面,找到pci域到cpu域的地址偏移,cpu域地址减去一个偏移才是BAR寄存器要写入的值,res是cpu域地址,region是最终的pci域地址: