linux-5.10.110内核源码分析 - Freescale ls1012a pcie host驱动

1、dts pcie设备树

1.1、pcie设备树

pcie1: pcie@3400000 {
        compatible = "fsl,ls1012a-pcie";
        reg = <0x00 0x03400000 0x0 0x00100000   /* controller registers */
               0x40 0x00000000 0x0 0x00002000>; /* configuration space */
        reg-names = "regs", "config";
        interrupts = <0 118 0x4>, /* controller interrupt */
                     <0 117 0x4>; /* PME interrupt */
        interrupt-names = "aer", "pme";
        #address-cells = <3>;
        #size-cells = <2>;
        device_type = "pci";
        num-viewport = <2>;
        bus-range = <0x0 0xff>;
        ranges = <0x81000000 0x0 0x00000000 0x40 0x00010000 0x0 0x00010000   /* downstream I/O */
                  0x82000000 0x0 0x40000000 0x40 0x40000000 0x0 0x40000000>; /* non-prefetchable memory */
        msi-parent = <&msi>;
        #interrupt-cells = <1>;
        interrupt-map-mask = <0 0 0 7>;
        interrupt-map = <0000 0 0 1 &gic 0 110 IRQ_TYPE_LEVEL_HIGH>,
                        <0000 0 0 2 &gic 0 111 IRQ_TYPE_LEVEL_HIGH>,
                        <0000 0 0 3 &gic 0 112 IRQ_TYPE_LEVEL_HIGH>,
                        <0000 0 0 4 &gic 0 113 IRQ_TYPE_LEVEL_HIGH>;
        status = "disabled";
};

        其中reg里面的configuration space [0x4000000000, 0x4000000000+0x2000)为配置空间的cpu地址,所有ep的配置空间的cpu地址都在这段地址空间,都写这段地址具体是操作总线上的那个设备是由pcie控制器的额外的寄存器也区分,也就是访问具体pcie配置空间的时候,需要把bus、device、function信息写入pcie控制器。后面具体代码后看到读写配置设备配置空间的操作。

1.2、pcie memory地址空间

        这段空间也就是cpu域地址空间,包含上面小节所介绍的configuration space以及I/O、memory空间,往这段地址空间读写数据会被pcie控制器转换为对pcie配置空间、I/O或者memory进行读写。具体的pcie协议在软件层面看不到,至于pcie协议里面的事务,完全由pcie控制器实现。读写这段地址空间具体读写哪个设备,后面会有更详细介绍。

2、RC配置空间读写

2.1、rc配置空间

rc的配置空间为pcie控制器的寄存器,各寄存器参考芯片手册的PCI_Express_Configuration_Registers memory map。

2.2、rc配置空间映射

        ls1012a pcie驱动调用devm_pci_remap_cfg_resource建立配置空间物理地址到虚拟地址的映射,这里都是cpu域的地址,不是cpu域到pcie域,函数栈如下:

        如上调用栈的__ioremap函数所示,phys_addr也就是0x3400000,最终虚拟地址会保存到pci->dbi_base变量中,后续通过pci->dbi_base这个虚拟地址来访问0x3400000 rc配置空间。

2.3、rc地址转换

        ls1012a使用DesignWare Cores PCI Express的ip,调用dw_pcie_own_conf_map_bus来获取cpu域地址;rc跟ep地址映射不一样,rc有自己的映射函数,从芯片手册看,rc的配置空间实际是以0x3400000为起始地址的一组pcie host寄存器,不需要cpu域地址到pcie域地址的转换,本质上获取该cpu域物理地址的虚拟地址即可,映射函数实现如下:

void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where)
{
	struct pcie_port *pp = bus->sysdata;
	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);

	if (PCI_SLOT(devfn) > 0)
		return NULL;

	return pci->dbi_base + where;
}

        devfn只能是rc,非rc的映射不走该函数,所以,"if (PCI_SLOT(devfn) > 0)"用于判断devfn是否是rc,非rc返回NULL;最后一行返回的映射地址很明显是上一小节0x3400000虚拟地址加上一个偏移,这个偏移也就是配置空间具体的某个寄存器。获取到cpu域地址之后就可以用这个地址像读写内存一样访问rc的配置空间了。

2.4、rc配置空间读写

        ls1012a pcie host配置空间寄存器如下:

        如下为ls1012a获取pcie host配置空间vendor id cpu域地址的调用栈:

        根据上面芯片手册显示的verdor id寄存器的偏移为0,也就是调用栈中的where,pci->dbi_base虚拟地址为0xffff800013200000(前面小节介绍的0x3400000的虚拟地址),那么函数返回的vendor id的虚拟地址即为0xffff800013200000+0;获取到cpu域地址的虚拟地址之后,读该虚拟地址即可获取配置空间的vendor id寄存器的值,调用栈如下:

        (__raw_readl参数addr即为vendor id寄存器cpu域的虚拟地址)

3、ep配置空间读写

3.1、resource offset

        ep配置空间的cpu域地址与pcie域地址并不相同,而是有一个线性映射关系,就像物理地址映射到虚拟地址一样,总线发送一个地址到pcie host控制器后,pcie host控制器需要对这个总线地址经过转换之后才是pcie域地址;在pcie dts设备树里面有描述cpu域到pcie域的地址映射,如下:

        如上红色方框所示的pcie地址和cpu地址,0x4040000000为cpu域地址,0x40000000为pcie域地址,也就是cpu发送0x4040000000的地址到pcie host控制器,pcie host控制器会把该地址转换为0x40000000,然后去访问对应的pcie设备,映射关系也就是cpu域地址加上一个固定的偏移即为pcie域地址,反过来,知道pcie域地址,用pcie域地址减去一个偏移就是cpu域地址。

3.2、pcie resource

        pcie的resource可以理解为dts中的ranges,也就是I/O、内存地址空间,内核通过pci_add_resource_offset函数把pcie的资源添加到pcie bridge结构体windows链表上,一个window为一段连续的地址空间,从代码上看,这段地址空间是cpu域地址空间,当然,里面有一个偏移,记录cpu域地址到pcie域地址的偏移,通过这个偏移可以获取到这段地址空间对应的pcie域地址空间。

        如下是non-prefetchable memory添加时resource参数的值:

        start为0x4040000000,也就是dts里面描述的cpu域地址,另外pci_add_resource_offset的offset的值为0x4000000000,也就是pcie域地址到cpu域地址的偏移,dts中的0x4040000000-0x40000000;添加non-prefetchable memory资源是函数调用栈如下:

        遍历dts ranges,添加资源到bridge代码如下:

        如上图黄色高亮行,res->start也就是dts里面的cpu域地址0x4040000000,range.pci_addr也就是dts里面的pcie域地址0x40000000,两个地址相减即为offset;resources为bridge->windows链表,在知道cpu域地址的时候,通过该链表找到cpu域地址的window,再获取该window的偏移,然后就能获取到该cpu域地址的pcie域地址。

3.3、ep配置空间映射(虚拟地址)

        在dts中,描述的pcie设备配置空间为[0x4000000000, 0x4000000000+0x00002000):

        pcie配置空间物理地址映射调用栈如下:

        __ioremap的phys_addr即为0x4000000000,也就是配置空间起始地址,size即为0x2000,也就是配置空间大小,ls1012a所有ep都是使用这一地址空间,之前有看到过其他cpu,在一段连续的cpu地址空间为每个ep分配一个配置空间,也就是起始地址加上一个n倍size的偏移即可获取每个设备的配置空间,ls1012a不是采用这种偏移的方式,而是需要先往pcie host控制器的寄存器写入当前要访问的ep的信息,然后由pcie host控制器根据寄存器信息将相同的cpu域地址转换成不同的ep的读写。

        __ioremap之后获取到虚拟地址将保存到pp->va_cfg0_base,之后通过访问这个虚拟地址来访问ep的配置空间。

3.4、ep配置空间映射

        ep配置空间映射通过调用dw_pcie_other_conf_map_bus函数实现,该函数调用dw_pcie_prog_outbound_atu写pcie host控制器,告诉pcie host控制器,接下来访问配置空间是访问哪个总线、设备、功能的配置空间,最终写pcie host控制器的函数代码如下:


static void __dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u8 func_no,
					int index, int type, u64 cpu_addr,
					u64 pci_addr, u32 size)
{
	u32 retries, val;

	if (pci->ops->cpu_addr_fixup)
		cpu_addr = pci->ops->cpu_addr_fixup(pci, cpu_addr);

	if (pci->iatu_unroll_enabled) {
		dw_pcie_prog_outbound_atu_unroll(pci, func_no, index, type,
						 cpu_addr, pci_addr, size);
		return;
	}

	dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
			   PCIE_ATU_REGION_OUTBOUND | index);
	dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
			   lower_32_bits(cpu_addr));
	dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
			   upper_32_bits(cpu_addr));
	dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
			   lower_32_bits(cpu_addr + size - 1));
	dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
			   lower_32_bits(pci_addr));
	dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
			   upper_32_bits(pci_addr));
	dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type |
			   PCIE_ATU_FUNC_NUM(func_no));
	dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);

	/*
	 * Make sure ATU enable takes effect before any subsequent config
	 * and I/O accesses.
	 */
	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
		val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
		if (val & PCIE_ATU_ENABLE)
			return;

		mdelay(LINK_WAIT_IATU);
	}
	dev_err(pci->dev, "Outbound iATU is not being enabled\n");
}

        参数cpu_addr的值为0x4000000000,也就是配置空间的cpu域地址,这里是配置空间的起始地址,不是需要访问的寄存器的地址,这里映射的一段空间;参数pci_addr的值为0x1000000,这里不是pci域地址,这里是bus、dev、func的一个组合,也就是告诉pcie host,写0x4000000000地址空间将要操作的是哪个总线的哪个设备的哪个功能,前面介绍了,所有ep的配置空间的cpu域地址都是一个地址空间,所以写pcie host寄存器,告诉pcie host当前这段配置地址空间要操作具体的哪个总线设备。

        写配置空间cpu域的起始地址:

        写配置空间cpu域的结束地址:

        写bus、dev、func:

        使能等待ATU就绪:

        ATU就绪之后,dw_pcie_other_conf_map_bus返回配置空间的虚拟地址即可:

        如下是读取pcie网卡vendor id地址映射的函数调用:

4、BAR地址空间分配

4.1、获取BAR空间大小

        配置空间映射前面已经介绍;BAR0配置空间的偏移为0x10h,读BAR0寄存器的函数调用栈如下:

        读写BAR0,获取BAR0大小的代码如下(读写BAR,获取地址位):

        计算BAR空间大小相关代码如下:

        最后会调用pcibios_bus_to_resource将pcie域地址转换为cpu域地址,就是前面介绍的pcie域地址加上到cpu域地址的偏移,最后保存到pcie设备的resource数组里面,此时resource里面的资源还不是最终的,只是根据pcie设备BAR寄存器默认值计算得来的。最后会有assign操作给BAR分配资源。

4.2、pcie资源分配

        资源分配简单的说就是分配地址空间,前面知道cpu域地址到pcie域地址有一个映射关系,知道pcie域地址的话,可以计算到cpu域地址,但是pcie设备是可热插拔并且pcie域地址是可配的,不同pcie设备默认pcie域地址可能会有冲突等情况,需要对他们的pcie域地址重新分配,分配之后写入BAR寄存器,具体可以参考《存储技术原理分析 - 基于Linux2.6内核源代码》,虽然内核版本比较老,但是还是具有参考意义。

        重新分配资源的调用栈如下:

        重新分配的代码如下:

        其中alloc.start为新分配的cpu域起始地址,此处值为0x4040000000,也就是cpu域的地址,根据前面的调用栈的resno的值,可以看到这是分配的BAR 2的资源,从内核启动打印的日志也可以印证这一点。

        分配完资源之后,还得写BAR寄存器;内核调用调用pci_std_update_resource写BAR寄存器,函数调用栈如下:

        首先调用pcibios_resource_to_bus函数将cpu域地址转换为pcie域地址,主要工作就是找到该资源在哪个windows,在哪段地址空间,也就是哪个cpu域映射到哪个pcie域地址,现在已经知道cpu域地址,再获取所在区间到pcie域地址的偏移,加上偏移即可获取pcie域地址,代码如下:

        获取到pcie域地址之后,将pcie域地址写入到BAR寄存器,调用栈如下:

        where的值为24,val的值为0x40000004,24即为BAR 2寄存器在配置空间的偏移,0x40000004即为pcie域地址(这里需要忽略低位,最终地址就是0x40000000),前面已经分析出cpu域地址为0x4040000000,cpu域地址与pcie域地址偏移以及地址正好与dts描述的一样。

        配置好BAR寄存器之后,就可以像对内存一样对BAR空间进行访问。读写0x4040000000地址的时候,pcie host控制器就会转换成对0x40000000地址的访问。

5、BAR空间读写

5.1、BAR空间映射

        BAR寄存器配置好之后,此时,pcie设备资源里面的地址还是物理地址,也就是前面的0x4040000000还是物理地址,需要建立页表映射才能访问,如下是rtl8111f BAR 2建立映射的调用栈:

        phys_addr即为0x4040000000,相关代码如下:

        没有找到rtl8111f芯片手册,根据代码分析,BAR 2应该是MAC Registers,获取到BAR 2映射的虚拟地址,就可以使用gdb像打印内存一样去打印这些寄存器的值,如下是在开发板打印的BAR 2相关寄存器的值:

        通过ifconfig查看rtl8111f网卡的mac地址如下:

        可以看到MAC地址正好与BAR 2前面几个bytes的内容相同。

  • 14
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值