linux PCI/PCIe驱动之assign_requested_resources_sorted的理解

1 assign_requested_resources_sorted

该函数是把要申请的资源, 按照对齐要求排序,最后从region中分配资源,配置到对应的PCI/PCIe设备的BAR配置空间中。
assign_requested_resources_sorted函数会调用pci_assign_resource函数,在pci_assign_resource函数中有两个需要关注的重要的函数,_pci_assign_resource和pci_update_resource,其调用过程,和对这两个函数的简单解释如下所示:

1.1 从初始化到资源配置的调用过程

|- pci_bus_size_bridges(bus);
|- pci_bus_assign_resources(bus);
  |- __pci_bus_assign_resources
    |- pbus_assign_resources_sorted
	  /* pci_dev->resource[]里记录有想申请的资源的大小, 
	  * 把这些资源按对齐的要求排序
	  * 比如资源A要求1K地址对齐,资源B要求32地址对齐
	  * 那么资源A排在资源B前面, 优先分配资源A
	  */
	  list_for_each_entry(dev, &bus->devices, bus_list)
	  	__dev_sort_resources(dev, &head);
	  // 分配资源
	  |- __assign_resources_sorted
          |- assign_requested_resources_sorted(head, &local_fail_head);
              |- pci_assign_resource
                  |- ret = _pci_assign_resource(dev, resno, size, align);
                  	  // 分配地址空间
                      |- __pci_assign_resource
                          |- pci_bus_alloc_resource
                              |- pci_bus_alloc_from_region
                                  /* Ok, try it out.. */
                                  |- ret = allocate_resource(r, res, size, ...);
                                      |- err = find_resource(root, new, size,...);
                                          |- __find_resource
                                              
                                              // 从资源链表中分配地址空间
                                              // 设置pci_dev->resource[]
                                              new->start = alloc.start;
                                              new->end = alloc.end;
                  // 把对应的PCI地址写入BAR
                  |- pci_update_resource(dev, resno);
                      |- pci_std_update_resource
                          /* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset 
                           * 写入BAR
                           */
                          pcibios_resource_to_bus(dev->bus, &region, res);
                          new = region.start;
                          reg = PCI_BASE_ADDRESS_0 + 4 * resno;
                          pci_write_config_dword(dev, reg, new);

1.2 _pci_assign_resource

在_pci_assign_resource函数中,最终要的函数是__find_resource,该函数在资源树中找到具有给定范围和对齐约束的资源。

1.2.1 调用过程

assign_requested_resources_sorted(head, &local_fail_head);
    pci_assign_resource
        ret = _pci_assign_resource(dev, resno, size, align);
        	// 分配地址空间
            __pci_assign_resource
                pci_bus_alloc_resource
                    pci_bus_alloc_from_region
                        /* Ok, try it out.. */
                        ret = allocate_resource(r, res, size, ...);
                            err = find_resource(root, new, size,...);
                                __find_resource
                                    // 从资源链表中分配地址空间
                                    // 设置pci_dev->resource[]
                                    new->start = alloc.start;
                                    new->end = alloc.end;

1.2.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;
	/*
	 * 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) {
		tmp.start = (this == old) ? old->start : this->end + 1;
		this = this->sibling;
	}
	for(;;) {
		if (this)
			tmp.end = (this == old) ?  this->end : this->start - 1;
		else
			tmp.end = root->end;

		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;
		avail.flags = new->flags & ~IORESOURCE_UNSET;
		if (avail.start >= tmp.start) {
			alloc.flags = avail.flags;
			alloc.start = constraint->alignf(constraint->alignf_data, &avail,
					size, constraint->align);
			alloc.end = alloc.start + size - 1;
			if (alloc.start <= alloc.end &&
			    resource_contains(&avail, &alloc)) {
				new->start = alloc.start;
				new->end = alloc.end;
				return 0;
			}
		}

next:		if (!this || this->end == root->end)
			break;

		if (this != old)
			tmp.start = this->end + 1;
		this = this->sibling;
	}
	return -EBUSY;
}

1.3 pci_update_resource

pci_update_resource函数最重要的功能是把前面在_pci_assign_resource函数处理流程中最终通过调用pci_std_update_resource函数把给设备分配的资源配置到对应的设备BAR空间中。

1.3.1 调用过程

assign_requested_resources_sorted(head, &local_fail_head);
    pci_assign_resource
        // 把对应的PCI地址写入BAR
        pci_update_resource(dev, resno);
            pci_std_update_resource
                /* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset 
                 * 写入BAR
                 */
                pcibios_resource_to_bus(dev->bus, &region, res);
                new = region.start;
                reg = PCI_BASE_ADDRESS_0 + 4 * resno;
                pci_write_config_dword(dev, reg, new);

1.3.2 pci_std_update_resource函数实现

在pci_std_update_resource函数中,需要重点关注两部分

  • pcibios_resource_to_bus该函数是在region资源中查找和分配的资源匹配类型并且包含设备需要的资源。
  • 将前面分配的资源的起始地址写入设备对应的BAR寄存器空间中
    • reg = PCI_BASE_ADDRESS_0 + 4 * resno;
    • pci_write_config_dword(dev, reg, new);
static void pci_std_update_resource(struct pci_dev *dev, int resno)
{
	struct pci_bus_region region;
	bool disable;
	u16 cmd;
	u32 new, check, mask;
	int reg;
	struct resource *res = dev->resource + resno;

	/* Per SR-IOV spec 3.4.1.11, VF BARs are RO zero */
	if (dev->is_virtfn)
		return;

	/*
	 * Ignore resources for unimplemented BARs and unused resource slots
	 * for 64 bit BARs.
	 */
	if (!res->flags)
		return;

	if (res->flags & IORESOURCE_UNSET)
		return;

	/*
	 * Ignore non-moveable resources.  This might be legacy resources for
	 * which no functional BAR register exists or another important
	 * system resource we shouldn't move around.
	 */
	if (res->flags & IORESOURCE_PCI_FIXED)
		return;

	pcibios_resource_to_bus(dev->bus, &region, res);
	new = region.start;

	if (res->flags & IORESOURCE_IO) {
		mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
		new |= res->flags & ~PCI_BASE_ADDRESS_IO_MASK;
	} else if (resno == PCI_ROM_RESOURCE) {
		mask = PCI_ROM_ADDRESS_MASK;
	} else {
		mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
		new |= res->flags & ~PCI_BASE_ADDRESS_MEM_MASK;
	}

	if (resno < PCI_ROM_RESOURCE) {
		reg = PCI_BASE_ADDRESS_0 + 4 * resno;
	} else if (resno == PCI_ROM_RESOURCE) {

		/*
		 * Apparently some Matrox devices have ROM BARs that read
		 * as zero when disabled, so don't update ROM BARs unless
		 * they're enabled.  See https://lkml.org/lkml/2005/8/30/138.
		 */
		if (!(res->flags & IORESOURCE_ROM_ENABLE))
			return;

		reg = dev->rom_base_reg;
		new |= PCI_ROM_ADDRESS_ENABLE;
	} else
		return;

	/*
	 * We can't update a 64-bit BAR atomically, so when possible,
	 * disable decoding so that a half-updated BAR won't conflict
	 * with another device.
	 */
	disable = (res->flags & IORESOURCE_MEM_64) && !dev->mmio_always_on;
	if (disable) {
		pci_read_config_word(dev, PCI_COMMAND, &cmd);
		pci_write_config_word(dev, PCI_COMMAND,
				      cmd & ~PCI_COMMAND_MEMORY);
	}

	pci_write_config_dword(dev, reg, new);
	pci_read_config_dword(dev, reg, &check);

	if ((new ^ check) & mask) {
		pci_err(dev, "BAR %d: error updating (%#08x != %#08x)\n",
			resno, new, check);
	}

	if (res->flags & IORESOURCE_MEM_64) {
		new = region.start >> 16 >> 16;
		pci_write_config_dword(dev, reg + 4, new);
		pci_read_config_dword(dev, reg + 4, &check);
		if (check != new) {
			pci_err(dev, "BAR %d: error updating (high %#08x != %#08x)\n",
				resno, new, check);
		}
	}

	if (disable)
		pci_write_config_word(dev, PCI_COMMAND, cmd);
}
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值