背景

PCIe设备枚举完成后,PCI总线号已经分配,PCIe ecam的映射、PCIe设备信息、bar的个数以及大小等已经ready,但是此时并没有给PCI device的bar、IO、mem分配资源。

这时就需要走到PCIe的资源分配流程,整个资源分配的过程就是从系统的总资源里给每个PCI device的bar分配资源。给每个PCI桥的base、limit的寄存器分配资源。其资源分配的入口是pci_bus_assign_resources()。

资源分配流程

dw_pcie_host_init
    |--->pci_bus_size_bridges(__pci_bus_size_bridges)
    |        |--->pci_bus_size_cardbus
    |        |--->__pci_bus_size_bridges
    |        |--->pci_bridge_check_ranges
    |        |--->pbus_size_io
    |        |--->pbus_size_mem
    |--->pci_bus_assign_resources ---> __pci_bus_assign_resources 
    
    
__pci_bus_assign_resources
    |--->pbus_assign_resources_sorted
    |        |--->__dev_sort_resources
    |        |        |--->pdev_sort_resources
    |        |--->__assign_resources_sorted 
    |                 |--->assign_requested_resources_sorted  
    |                         |--->pci_assign_resource   
    |                                 |--->_pci_assign_resource
    |                                         |--->__pci_assign_resource  
    |                                                 |--->pci_bus_alloc_resource
    |                                                         |--->pci_bus_alloc_from_region
    |                                                                 |--->allocate_resource
    |                                                                         |--->find_resource
    |                                                                         |--->__request_resource
    |--->__request_resource
    |--->__pci_bus_assign_resources(递归回调)
    |--->pci_setup_bridge

__pci_bus_assign_resources

void __pci_bus_assign_resources(const struct pci_bus *bus,
                                struct list_head *realloc_head,
                                struct list_head *fail_head)
{
        struct pci_bus *b;
        struct pci_dev *dev;

/* bus:0, 会对该bus上的所有device分别调用__dev_sort_resource
 * 然后统一调用一个__assign_resources_sorted()。
 * 之后程序进入下面的list中,又会嵌套进入__pci_bus_assign_resources, 
 * 这时bus:1。重复上面的过程。
 * 在bus:1是__pci_bus_assign_resources
 * 在处理处理完pbus_assign_resources_sorted后不回往下执行,返回上层。
 * 这时bus:0, 进入pci_setup_bridge执行。
 * 其中,会在pbus_assign_resources_sorted中配置BAR,
 * 在__pci_setup_bridge中配mem, I/O, prefetch mem的base/limit
 */

        // 最后调用pci_update_resource()将初始化pcie bar寄存器,
        // 将更新的资源区间写到寄存器
        // __assign_resources_sorted
        //        |--->assign_requested_resources_sorted
        //                |--->pci_assign_resource
        //                        |--->pci_update_resource
        pbus_assign_resources_sorted(bus, realloc_head, fail_head);

        list_for_each_entry(dev, &bus->devices, bus_list) {
                // 当前pci设备分配pcie地址空间 --- request_resource
                pdev_assign_fixed_resources(dev);

                b = dev->subordinate;
                if (!b)
                        continue;

                // 此处的一个小细节:
                // pbus_assign_resources_sorted 函数是在此函数递归调用之前执行,
                // 而 pci_setup_bridge 函数 在递归调用之后执行
                // Linux PCI采用这种方式,可以保证PCI设备BAR寄存器初始化
                // 是从上游 PCI总线 到 下游 PCI总线,
                // 而PCI桥Base、Limit寄存器的初始化是从下游PCI总线到上游PCI总线
                __pci_bus_assign_resources(b, realloc_head, fail_head);
                

                switch (dev->hdr_type) {
                case PCI_HEADER_TYPE_BRIDGE:
                        if (!pci_is_enabled(dev))
                                pci_setup_bridge(b);
                        break;

                case PCI_HEADER_TYPE_CARDBUS:
                        pci_setup_cardbus(b);
                        break;

                default:
                        pci_info(dev, "not setting up bridge for bus %04x:%02x\n",
                                 pci_domain_nr(b), b->number);
                        break;
                }
        }
}

pbus_assign_resources_sorted

static void pbus_assign_resources_sorted(const struct pci_bus *bus,
                                         struct list_head *realloc_head,
                                         struct list_head *fail_head)
{
        struct pci_dev *dev;
        LIST_HEAD(head);

        /* 
         * pci_dev->resource[]里记录有想申请的资源的大小, 
         * 把这些资源按对齐的要求排序
         * 比如资源A要求1K地址对齐,资源B要求32地址对齐
         * 那么资源A排在资源B前面, 优先分配资源A
         */
        list_for_each_entry(dev, &bus->devices, bus_list) {
                // 将pci设备使用的资源进行对齐和排序,然后加入到head流程中
                __dev_sort_resources(dev, &head);
        }

        // 分配资源
        __assign_resources_sorted(&head, realloc_head, fail_head);
}
__dev_sort_resources
static void __dev_sort_resources(struct pci_dev *dev, struct list_head *head)
{
        u16 class = dev->class >> 8;

        /* Don't touch classless devices or host bridges or IOAPICs */
        if (class == PCI_CLASS_NOT_DEFINED || class == PCI_CLASS_BRIDGE_HOST)
                return;

        /* Don't touch IOAPIC devices already enabled by firmware */
        if (class == PCI_CLASS_SYSTEM_PIC) {
                u16 command;
                pci_read_config_word(dev, PCI_COMMAND, &command);
                if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY))
                        return;
        }

        /* 把pci_dev中的resource[]从大到小排序, 存入链表head中 */
        pdev_sort_resources(dev, head);
}
__assign_resources_sorted
static void __assign_resources_sorted(struct list_head *head,
                                      struct list_head *realloc_head,
                                      struct list_head *fail_head)
{
        /*
         * Should not assign requested resources at first.  They could be
         * adjacent, so later reassign can not reallocate them one by one in
         * parent resource window.
         *
         * Try to assign requested + add_size at beginning.  If could do that,
         * could get out early.  If could not do that, we still try to assign
         * requested at first, then try to reassign add_size for some resources.
         *
         * Separate three resource type checking if we need to release
         * assigned resource after requested + add_size try.
         *
         *        1. If IO port assignment fails, will release assigned IO
         *           port.
         *        2. If pref MMIO assignment fails, release assigned pref
         *           MMIO.  If assigned pref MMIO's parent is non-pref MMIO
         *           and non-pref MMIO assignment fails, will release that
         *           assigned pref MMIO.
         *        3. If non-pref MMIO assignment fails or pref MMIO
         *           assignment fails, will release assigned non-pref MMIO.
         */
        LIST_HEAD(save_head);
        LIST_HEAD(local_fail_head);
        struct pci_dev_resource *save_res;
        struct pci_dev_resource *dev_res, *tmp_res, *dev_res2;
        unsigned long fail_type;
        resource_size_t add_align, align;

        /* Check if optional add_size is there */
        /* 因为realloc_head为空链表,所以直接到requested_and_reassign */
        if (!realloc_head || list_empty(realloc_head))
                goto requested_and_reassign;

        /* Save original start, end, flags etc at first */
        list_for_each_entry(dev_res, head, list) {
                if (add_to_list(&save_head, dev_res->dev, dev_res->res, 0, 0)) {
                        free_list(&save_head);
                        goto requested_and_reassign;
                }
        }

......

requested_and_reassign:
        /* Satisfy the must-have resource requests */
        assign_requested_resources_sorted(head, fail_head);

        /* Try to satisfy any additional optional resource requests */
        if (realloc_head)
                reassign_resources_sorted(realloc_head, head);
        free_list(head);
}
assign_requested_resources_sorted
/**
 * assign_requested_resources_sorted() - Satisfy resource requests
 *
 * @head:        Head of the list tracking requests for resources
 * @fail_head:        Head of the list tracking requests that could not be
 *                allocated
 *
 * Satisfy resource requests of each element in the list.  Add requests that
 * could not be satisfied to the failed_list.
 */
static void assign_requested_resources_sorted(struct list_head *head,
                                 struct list_head *fail_head)
{
        struct resource *res;
        struct pci_dev_resource *dev_res;
        int idx;

        /* 
         * dev_res(pci_dev_resource)存储一个device中一个配置空间
         * 的资源(一个设备可有多个mem或I/O配置空间)
         */
        list_for_each_entry(dev_res, head, list) {
                res = dev_res->res;
                idx = res - &dev_res->dev->resource[0];
                if (resource_size(res) &&
                    pci_assign_resource(dev_res->dev, idx)) {
                        if (fail_head) {
                                /*
                                 * If the failed resource is a ROM BAR and
                                 * it will be enabled later, don't add it
                                 * to the list.
                                 */
                                if (!((idx == PCI_ROM_RESOURCE) &&
                                      (!(res->flags & IORESOURCE_ROM_ENABLE))))
                                        add_to_list(fail_head,
                                                    dev_res->dev, res,
                                                    0 /* don't care */,
                                                    0 /* don't care */);
                        }
                        reset_resource(res);
                }
        }
}
pci_assign_resource
int pci_assign_resource(struct pci_dev *dev, int resno)
{
        struct resource *res = dev->resource + resno;
        resource_size_t align, size;
        int ret;

        if (res->flags & IORESOURCE_PCI_FIXED)
                return 0;

        res->flags |= IORESOURCE_UNSET;
        align = pci_resource_alignment(dev, res);
        if (!align) {
                pci_info(dev, "BAR %d: can't assign %pR (bogus alignment)\n",
                         resno, res);
                return -EINVAL;
        }

        size = resource_size(res);
        // 分配地址空间
        //_pci_assign_resource
        //        |--->__pci_assign_resource
        //                |--->pci_bus_alloc_resource
        //                        |--->pci_bus_alloc_from_region
        //                                |--->allocate_resource
        //                                        |--->find_resource
        //                                                |--->__find_resource
        ret = _pci_assign_resource(dev, resno, size, align);

        /*
         * If we failed to assign anything, let's try the address
         * where firmware left it.  That at least has a chance of
         * working, which is better than just leaving it disabled.
         */
        if (ret < 0) {
                pci_info(dev, "BAR %d: no space for %pR\n", resno, res);
                ret = pci_revert_fw_address(res, dev, resno, size);
        }

        if (ret < 0) {
                pci_info(dev, "BAR %d: failed to assign %pR\n", resno, res);
                return ret;
        }

        res->flags &= ~IORESOURCE_UNSET;
        res->flags &= ~IORESOURCE_STARTALIGN;
        pci_info(dev, "BAR %d: assigned %pR\n", resno, res);

        // 将初始化pcie bar寄存器,将更新的资源区间写到寄存器,把对应的PCI地址写入BAR
        if (resno < PCI_BRIDGE_RESOURCES)
                pci_update_resource(dev, resno);

        return 0;
}

_pci_assign_resource

static int _pci_assign_resource(struct pci_dev *dev, int resno,
                                resource_size_t size, resource_size_t min_align)
{
        struct pci_bus *bus;
        int ret;

        bus = dev->bus;
        while ((ret = __pci_assign_resource(bus, dev, resno, size, min_align))) {
                if (!bus->parent || !bus->self->transparent)
                        break;
                bus = bus->parent;
        }

        return ret;
}
static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev,
                int resno, resource_size_t size, resource_size_t align)
{
        struct resource *res = dev->resource + resno;
        resource_size_t min;
        int ret;

        min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM;

        /*
         * First, try exact prefetching match.  Even if a 64-bit
         * prefetchable bridge window is below 4GB, we can't put a 32-bit
         * prefetchable resource in it because pbus_size_mem() assumes a
         * 64-bit window will contain no 32-bit resources.  If we assign
         * things differently than they were sized, not everything will fit.
         */
        ret = pci_bus_alloc_resource(bus, res, size, align, min,
                                     IORESOURCE_PREFETCH | IORESOURCE_MEM_64,
                                     pcibios_align_resource, dev);
        if (ret == 0)
                return 0;

        /*
         * If the prefetchable window is only 32 bits wide, we can put
         * 64-bit prefetchable resources in it.
         */
        if ((res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) ==
             (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) {
                ret = pci_bus_alloc_resource(bus, res, size, align, min,
                                             IORESOURCE_PREFETCH,
                                             pcibios_align_resource, dev);
                if (ret == 0)
                        return 0;
        }

        /*
         * If we didn't find a better match, we can put any memory resource
         * in a non-prefetchable window.  If this resource is 32 bits and
         * non-prefetchable, the first call already tried the only possibility
         * so we don't need to try again.
         */
        if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64))
                ret = pci_bus_alloc_resource(bus, res, size, align, min, 0,
                                             pcibios_align_resource, dev);

        return ret;
}
/**
 * pci_bus_alloc_resource - allocate a resource from a parent bus
 * @bus: PCI bus
 * @res: resource to allocate
 * @size: size of resource to allocate
 * @align: alignment of resource to allocate
 * @min: minimum /proc/iomem address to allocate
 * @type_mask: IORESOURCE_* type flags
 * @alignf: resource alignment function
 * @alignf_data: data argument for resource alignment function
 *
 * Given the PCI bus a device resides on, the size, minimum address,
 * alignment and type, try to find an acceptable resource allocation
 * for a specific device resource.
 */
int pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
                resource_size_t size, resource_size_t align,
                resource_size_t min, unsigned long type_mask,
                resource_size_t (*alignf)(void *,
                                          const struct resource *,
                                          resource_size_t,
                                          resource_size_t),
                void *alignf_data)
{
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
        int rc;

        if (res->flags & IORESOURCE_MEM_64) {
                rc = pci_bus_alloc_from_region(bus, res, size, align, min,
                                               type_mask, alignf, alignf_data,
                                               &pci_high);
                if (rc == 0)
                        return 0;

                return pci_bus_alloc_from_region(bus, res, size, align, min,
                                                 type_mask, alignf, alignf_data,
                                                 &pci_64_bit);
        }
#endif

        return pci_bus_alloc_from_region(bus, res, size, align, min,
                                         type_mask, alignf, alignf_data,
                                         &pci_32_bit);
}
static int pci_bus_alloc_from_region(struct pci_bus *bus, struct resource *res,
                resource_size_t size, resource_size_t align,
                resource_size_t min, unsigned long type_mask,
                resource_size_t (*alignf)(void *,
                                          const struct resource *,
                                          resource_size_t,
                                          resource_size_t),
                void *alignf_data,
                struct pci_bus_region *region)
{
        int i, ret;
        struct resource *r, avail;
        resource_size_t max;

        type_mask |= IORESOURCE_TYPE_BITS;

        pci_bus_for_each_resource(bus, r, i) {
                resource_size_t min_used = min;

                if (!r)
                        continue;

                /* type_mask must match */
                if ((res->flags ^ r->flags) & type_mask)
                        continue;

                /* We cannot allocate a non-prefetching resource
                   from a pre-fetching area */
                if ((r->flags & IORESOURCE_PREFETCH) &&
                    !(res->flags & IORESOURCE_PREFETCH))
                        continue;

                avail = *r;
                pci_clip_resource_to_region(bus, &avail, region);

                /*
                 * "min" is typically PCIBIOS_MIN_IO or PCIBIOS_MIN_MEM to
                 * protect badly documented motherboard resources, but if
                 * this is an already-configured bridge window, its start
                 * overrides "min".
                 */
                if (avail.start)
                        min_used = avail.start;

                max = avail.end;

                /* Ok, try it out.. */
                ret = allocate_resource(r, res, size, min_used, max,
                                        align, alignf, alignf_data);
                if (ret == 0)
                        return 0;
        }
        return -ENOMEM;
}
/**
 * allocate_resource - allocate empty slot in the resource tree given range & alignment.
 *         The resource will be reallocated with a new size if it was already allocated
 * @root: root resource descriptor
 * @new: resource descriptor desired by caller
 * @size: requested resource region size
 * @min: minimum boundary to allocate
 * @max: maximum boundary to allocate
 * @align: alignment requested, in bytes
 * @alignf: alignment function, optional, called if not NULL
 * @alignf_data: arbitrary data to pass to the @alignf function
 */
int allocate_resource(struct resource *root, struct resource *new,
                      resource_size_t size, resource_size_t min,
                      resource_size_t max, resource_size_t align,
                      resource_size_t (*alignf)(void *,
                                                const struct resource *,
                                                resource_size_t,
                                                resource_size_t),
                      void *alignf_data)
{
        int err;
        struct resource_constraint constraint;

        if (!alignf)
                alignf = simple_align_resource;

        constraint.min = min;
        constraint.max = max;
        constraint.align = align;
        constraint.alignf = alignf;
        constraint.alignf_data = alignf_data;

        if ( new->parent ) {
                /* resource is already allocated, try reallocating with
                   the new constraints */
                return reallocate_resource(root, new, size, &constraint);
        }

        write_lock(&resource_lock);
        err = find_resource(root, new, size, &constraint);
        if (err >= 0 && __request_resource(root, new))
                err = -EBUSY;
        write_unlock(&resource_lock);
        return err;
}

pci_update_resource

void pci_update_resource(struct pci_dev *dev, int resno)
{
        if (resno <= PCI_ROM_RESOURCE)
                pci_std_update_resource(dev, resno);
#ifdef CONFIG_PCI_IOV
        else if (resno >= PCI_IOV_RESOURCES && resno <= PCI_IOV_RESOURCE_END)
                pci_iov_update_resource(dev, resno);
#endif
}
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;

        /* 
         * 在region资源中查找和分配的资源匹配类型并且包含设备需要的资源
         * 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset 写入BAR
         */
        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);
        }

        // 更新 resource 的低 32bit 到 PCI_BASE_ADDRESS_0 + 4 * resno
        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);
        }

        // 更新 MEM64 的高 32bit 到 PCI_BASE_ADDRESS_0 + 4 * resno + 4
        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);
}

小结

总而言之,pcie的资源枚举过程可以概况如下:

  • 获取上游pci 桥设备所管理的系统资源范围
  • 使用DFS(深度优先搜索)对所有的pci ep device进行bar资源的分配
  • 使用DFS(深度优先搜索)对当前pci桥设备的base和limit的值,并对这些寄存器进行更新。

至此,pci树中所有pci设备的BAR寄存器,以及pci桥的base、limit寄存器都已经初始化完毕。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐