主页 > 电脑硬件  > 

linux-5.10.110内核源码分析-bcm2711pcieBAR地址分配

linux-5.10.110内核源码分析-bcm2711pcieBAR地址分配
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域地址:

4.2、更新BAR(pci_std_update_resource)

标签:

linux-5.10.110内核源码分析-bcm2711pcieBAR地址分配由讯客互联电脑硬件栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“linux-5.10.110内核源码分析-bcm2711pcieBAR地址分配