linux PCI/PCIe驱动之pci_read_bases的理解

1 pci_read_bases

设备枚举的流程如下所示,在pci_read_bases函数中会去读取PCIe设备的相关信息,主要是涉及到PCI/PCIe设备所需要的资源大小信息。

1.1 pci_read_bases的调用流程

    pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
		pci_scan_root_bus_msi
            pci_scan_child_bus
            	pci_scan_slot
            		dev = pci_scan_single_device(bus, devfn);
						dev = pci_scan_device(bus, devfn);
							struct pci_dev *dev;
							dev = pci_alloc_dev(bus);
							pci_setup_device
                                pci_read_bases(dev, 6, PCI_ROM_ADDRESS);	
                        pci_device_add(dev, bus);

1.2 pci_read_bases函数定义

  • pci_read_bases首先会依次遍历howmany PCI/PCIe设备的BAR配置
  • struct resource *res = &dev->resource[pos]获取对应的PCI/PCIe设备对应的BAR的resource结构体,为后面的获取到资源信息做填充使用
  • __pci_read_base用于具体的PCI/PCIe设备资源获取和解析,稍后会进一步解析
  • 如果rom是有效的,则会进一步去解析PCI_ROM_RESOURCE的扩展配置空间的资源信息( #6: expansion ROM resource )
static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom)
{
	unsigned int pos, reg;

	if (dev->non_compliant_bars)
		return;

	/* Per PCIe r4.0, sec 9.3.4.1.11, the VF BARs are all RO Zero */
	if (dev->is_virtfn)
		return;

	for (pos = 0; pos < howmany; pos++) {
		struct resource *res = &dev->resource[pos];
		reg = PCI_BASE_ADDRESS_0 + (pos << 2);
		pos += __pci_read_base(dev, pci_bar_unknown, res, reg);
	}

	if (rom) {
		struct resource *res = &dev->resource[PCI_ROM_RESOURCE];
		dev->rom_base_reg = rom;
		res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH |
				IORESOURCE_READONLY | IORESOURCE_SIZEALIGN;
		__pci_read_base(dev, pci_bar_mem32, res, rom);
	}
}

1.3 __pci_read_base

  • 和函数本身的声明的含义一致,读取PCI/PCIe设备的BAR寄存器,解析PCI/PCIe设备的属性以及资源需求信息
  • 读取BAR寄存器的过程
    • 读BAR,保留原值
    • 写0xFFFFFFFF到BAR
    • 在读出来,解析出所需要的地址空间大小,记录在pci_dev->resource[ ]里
      • pci_dev->resource[ ].start = 0;
      • pci_dev->resource[ ].end = size - 1;
    • 将原值写会BAR寄存器中
  • 根据上一步读取出的l和sz去计算设备当前BAR所需要资源的基址和大小,通过pcibios_bus_to_resource配置到对应设备BAR的resource里面,待稍后的资源分配使用
/**
 * pci_read_base - Read a PCI BAR
 * @dev: the PCI device
 * @type: type of the BAR
 * @res: resource buffer to be filled in
 * @pos: BAR position in the config space
 *
 * Returns 1 if the BAR is 64-bit, or 0 if 32-bit.
 */
int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
		    struct resource *res, unsigned int pos)
{
	u32 l = 0, sz = 0, mask;
	u64 l64, sz64, mask64;
	u16 orig_cmd;
	struct pci_bus_region region, inverted_region;pci_bus_type 

	mask = type ? PCI_ROM_ADDRESS_MASK : ~0;

	/* No printks while decoding is disabled! */
	if (!dev->mmio_always_on) {
		pci_read_config_word(dev, PCI_COMMAND, &orig_cmd);
		if (orig_cmd & PCI_COMMAND_DECODE_ENABLE) {
			pci_write_config_word(dev, PCI_COMMAND,
				orig_cmd & ~PCI_COMMAND_DECODE_ENABLE);
		}
	}

	res->name = pci_name(dev);

	/* 读取BAR寄存器的过程 */
	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);

	/*
	 * All bits set in sz means the device isn't working properly.
	 * If the BAR isn't implemented, all bits must be 0.  If it's a
	 * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit
	 * 1 must be clear.
	 */
	if (sz == 0xffffffff)
		sz = 0;

	/*
	 * I don't know how l can have all bits set.  Copied from old code.
	 * Maybe it fixes a bug on some ancient platform.
	 */
	if (l == 0xffffffff)
		l = 0;

	/* 对于初始不知道是什么类型PCI/PCIe 设备的,采用的是pci_bar_unknown的配置 */
	if (type == pci_bar_unknown) {
		res->flags = decode_bar(dev, l);
		res->flags |= IORESOURCE_SIZEALIGN;
		/* 对于IO类型的设备的配置解析 */
		if (res->flags & IORESOURCE_IO) {
			/* l64表示数据原始配置信息,sz64表示的是IO设备需要的资源大小 */
			l64 = l & PCI_BASE_ADDRESS_IO_MASK;
			sz64 = sz & PCI_BASE_ADDRESS_IO_MASK;
			mask64 = PCI_BASE_ADDRESS_IO_MASK & (u32)IO_SPACE_LIMIT;
		} else {
			/* 对于MEM设备l64表示数据原始配置信息,sz64表示的是MEM设备需要的资源大小 */
			l64 = l & PCI_BASE_ADDRESS_MEM_MASK;
			sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;
			mask64 = (u32)PCI_BASE_ADDRESS_MEM_MASK;
		}
	} else {
		/* 对于其他已知类型的设备则直接走该流程 */
		if (l & PCI_ROM_ADDRESS_ENABLE)
			res->flags |= IORESOURCE_ROM_ENABLE;
		l64 = l & PCI_ROM_ADDRESS_MASK;
		sz64 = sz & PCI_ROM_ADDRESS_MASK;
		mask64 = PCI_ROM_ADDRESS_MASK;
	}

	/* 对于MEM64的设备,两个相邻的BAR组成一个对应的64位的有效地址,
	   该处所读取出的配置作为对应的64位地址的高位,而MEM大小也作为高位 */
	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);
	}

	if (!dev->mmio_always_on && (orig_cmd & PCI_COMMAND_DECODE_ENABLE))
		pci_write_config_word(dev, PCI_COMMAND, orig_cmd);

	if (!sz64)
		goto fail;

	/* 按照对齐的要求去调整PCI/PCIe设备的大小 */
	sz64 = pci_size(l64, sz64, mask64);
	if (!sz64) {
		pci_info(dev, FW_BUG "reg 0x%x: invalid BAR (can't size)\n",
			 pos);
		goto fail;
	}

	if (res->flags & IORESOURCE_MEM_64) {
		if ((sizeof(pci_bus_addr_t) < 8 || sizeof(resource_size_t) < 8)
		    && sz64 > 0x100000000ULL) {
			res->flags |= IORESOURCE_UNSET | IORESOURCE_DISABLED;
			res->start = 0;
			res->end = 0;
			pci_err(dev, "reg 0x%x: can't handle BAR larger than 4GB (size %#010llx)\n",
				pos, (unsigned long long)sz64);
			goto out;
		}

		if ((sizeof(pci_bus_addr_t) < 8) && l) {
			/* Above 32-bit boundary; try to reallocate */
			res->flags |= IORESOURCE_UNSET;
			res->start = 0;
			res->end = sz64 - 1;
			pci_info(dev, "reg 0x%x: can't handle BAR above 4GB (bus address %#010llx)\n",
				 pos, (unsigned long long)l64);
			goto out;
		}
	}

	region.start = l64;
	region.end = l64 + sz64 - 1;

	pcibios_bus_to_resource(dev->bus, res, &region);
	pcibios_resource_to_bus(dev->bus, &inverted_region, res);

	/*
	 * If "A" is a BAR value (a bus address), "bus_to_resource(A)" is
	 * the corresponding resource address (the physical address used by
	 * the CPU.  Converting that resource address back to a bus address
	 * should yield the original BAR value:
	 *
	 *     resource_to_bus(bus_to_resource(A)) == A
	 *
	 * If it doesn't, CPU accesses to "bus_to_resource(A)" will not
	 * be claimed by the device.
	 */
	if (inverted_region.start != region.start) {
		res->flags |= IORESOURCE_UNSET;
		res->start = 0;
		res->end = region.end - region.start;
		pci_info(dev, "reg 0x%x: initial BAR value %#010llx invalid\n",
			 pos, (unsigned long long)region.start);
	}

	goto out;


fail:
	res->flags = 0;
out:
	if (res->flags)
		pci_info(dev, "reg 0x%x: %pR\n", pos, res);

	return (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
}

1.4 pcibios_bus_to_resource

  • 在该函数中所使用的的bridge->windows所对应的资源数据都是在pci_parse_request_of_pci_ranges函数在初始化过程中解析出来的,在ARM体系结构中由于一般情况下PCI域地址和处理器域地址是一致的,所以通常情况下window->offset的值也为0
  • res->start = region->start + offset和res->end = region->end + offset在稍后的资源分配中需要用到,作为资源分配的依据。
void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res,
			     struct pci_bus_region *region)
{
	struct pci_host_bridge *bridge = pci_find_host_bridge(bus);
	struct resource_entry *window;
	resource_size_t offset = 0;

	resource_list_for_each_entry(window, &bridge->windows) {
		struct pci_bus_region bus_region;

		if (resource_type(res) != resource_type(window->res))
			continue;

		bus_region.start = window->res->start - window->offset;
		bus_region.end = window->res->end - window->offset;

		if (region_contains(&bus_region, region)) {
			offset = window->offset;
			break;
		}
	}

	res->start = region->start + offset;
	res->end = region->end + offset;
}
<think>好的,用户想了解Linux内核如何分配PCIe设备的物理内存。这需要我回顾相关知识,并组织一个清晰的解释流程。首先,我得从PCIe设备的初始化开始,可能需要提到枚举过程,然后是资源分配,特别是物理内存区域,如BARs的处理。 记得用户提到系统级指令,要确保所有数学公式正确使用$...$或$$...$$。虽然这个问题可能公式不多,但如果有涉及地址计算的地方,可能需要用LaTeX表达。例如,物理地址的计算或映射可能需要用到公式。 接下来,结构要清晰。分步骤说明整个过程:枚举设备、读取BAR、地址分配、映射到内核空间、可能的DMA设置。每个步骤需要详细说明,比如BAR的读取和扩展,内核如何管理资源以避免冲突,使用哪些数据结构,如pci_dev和resource。 还要注意用户要求使用中文回答,所以术语要准确翻译,比如“MMIO”翻译为“内存映射I/O”,“DMA”可以保留英文但说明中文名称。需要检查是否有遗漏的关键点,比如PCI配置空间访问,资源管理中的pci_bus_region,以及iomem_resource的作用。 另外,用户可能对DMA的地址分配感兴趣,比如一致性DMA和流式DMA的区别,这里需要简要提及。同时,确保每个部分逻辑连贯,例如从探测设备到资源请求,再到实际映射和DMA配置,整个过程环环相扣。 最后,总结整个流程,强调内核如何确保资源合理分配,避免冲突,以及设备驱动如何访问这些资源。可能还需要提到不同架构下的差异,如ARM和x86在地址空间处理上的不同,但用户问题可能更关注通用逻辑,所以可以简略。 检查是否有技术错误,比如PCIe设备在启动时由固件(如BIOS或UEFI)进行初始配置,但Linux内核在启动时会重新扫描和分配资源,确保没有依赖错误的假设。同时,确认提到的函数和数据结构名称是否正确,如pci_read_bases()、request_resource()等。 最终,确保回答符合用户的结构要求,分步骤说明,语言简洁,逻辑清晰,正确使用LaTeX格式,满足所有系统级指令和用户可见层的要求。</think>Linux内核对PCIe设备物理内存的分配是一个系统级过程,主要涉及以下几个关键步骤: $$以下流程基于Linux内核v5.10+架构通用实现$$ 1. **PCIe设备枚举** - 内核启动时通过ACPI/UEFI或直接扫描PCI总线 - 发现设备后创建`struct pci_dev`对象 - 读取设备配置空间中的BARs(Base Address Registers) ``` BAR0: 0xFE000000 (32-bit, prefetchable) BAR2: 0xFEB80000 (64-bit, non-prefetchable) ``` 2. **BAR解析与地址空间计算** - 通过`pci_read_bases()`解析BAR寄存器 - 确定请求的地址空间类型: - $Memory\ Space \in \{MMIO, PMIO\}$ - $Address\ Width \in \{32-bit, 64-bit\}$ - 计算所需内存大小:向BAR写入全1后读取掩码 3. **物理地址分配** - 调用`pci_bus_alloc_resource()`进行资源分配 - 通过`request_resource()`向内核资源树申请: ```c struct resource *res = &dev->resource[bar]; request_resource(&iomem_resource, res); ``` - 使用红黑树管理已分配区域,确保无重叠 4. **地址写入与映射** - 将分配的物理地址回写至设备BAR寄存器 $$ \text{BAR} = (\text{phys\_addr} \& \text{type\_mask}) | \text{enable\_bit} $$ - 建立内核虚拟地址映射: ```c void __iomem *addr = pci_iomap(pdev, bar, maxlen); ``` 5. **DMA地址空间处理** - 对需要DMA的设备: - 一致性DMA:`dma_alloc_coherent()` - 流式DMA:`dma_map_single()` - 根据架构实现IOMMU/SMMU映射 **关键数据结构关系**: ``` pci_dev → resource[] → iomem_resource ↓ pci_bus_region ↓ host bridge address window ``` **典型物理内存布局示例**: ``` 0x00000000-0x3FFFFFFF : System RAM 0x40000000-0x7FFFFFFF : PCI MMIO Space 0x80000000-0xFFFFFFFF : PCI Prefetchable ``` 该过程确保了: 1. 设备物理地址不与系统内存和其他设备冲突 2. 支持热插拔设备的动态资源分配 3. 兼容不同架构的地址空间布局(x86/ARM等) 4. 提供统一的设备访问接口给驱动程序 驱动开发者最终通过`ioremap()`或`pci_iomap()`获得可直接访问的虚拟地址,物理地址的实际分配对驱动透明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值