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

news2024/11/19 22:47:02

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的内容相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1899751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux-DNS

DNS域名解析服务 1.DNS介绍 DNS 是域名系统 (Domain Name System) 的缩写&#xff0c;是因特网的一项核心服务&#xff0c;它作为可以将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便的访问互联网&#xff0c;而不用去记住能够被机器直接读取的IP数串。…

乐鑫ESPRESSIF芯片开发简介

乐鑫科技&#xff08;Espressif Systems&#xff0c;通常简称乐鑫或ESPRESSIF&#xff09;是一家全球化的无晶圆厂半导体公司&#xff0c;专注于研发无线通信微控制器单元&#xff08;MCU&#xff09;芯片&#xff0c;特别在物联网&#xff08;IoT&#xff09;领域有着显著的影…

【CentOS 7.6】Linux版本 portainer本地镜像导入docker安装配置教程,不需要魔法拉取!(找不着镜像的来看我)

吐槽 我本来根本不想写这篇博客&#xff0c;但我很不解也有点生气&#xff0c;CSDN这么大没有人把现在需要魔法才能拉取的镜像放上来。 你们都不放&#xff0c;根本不方便。我来上传资源。 portainer-ce-latest.tar Linux/amd64 镜像下载地址&#xff1a; 链接&#xff1a;h…

windows下搭建python+jupyter notebook

一.下载python 下面网址下载python3 https://www.python.org/ 二. 安装jupyter notebook 三. 修改配置 四. 检测是否正常运行

【IT领域新生必看】 Java编程中的重写(Overriding)规则:初学者轻松掌握的全方位指南

文章目录 引言什么是方法重写&#xff08;Overriding&#xff09;&#xff1f;方法重写的基本示例 方法重写的规则1. 方法签名必须相同示例&#xff1a; 2. 返回类型可以是子类型&#xff08;协变返回类型&#xff09;示例&#xff1a; 3. 访问修饰符不能比父类的更严格示例&am…

《C++20设计模式》代理模式

文章目录 一、前言二、实现1、UML类图2、实现 一、前言 这代理模式和装饰器模式很像啊。都是套一层类。&#x1f630; 主要就是功能差别 装饰器&#xff1a; 为了强化原有类的功能。代理模式&#xff1a; 不改变原有功能&#xff0c;只是强化原有类的潜在行为。 我觉的书上有…

spark on k8s两种方式的原理与对比

spark on k8s两种方式的原理与对比 1、spark on k8s 方式 spark-submit可以直接用来向 Kubernetes 集群提交 Spark 应用&#xff0c;提交机制如下&#xff1a; 1、Spark 创建一个在Kubernetes pod中运行的 Spark 驱动程序。 2、驱动程序创建在 Kubernetes Pod 中运行的执行器…

Python创建MySQL数据库

一、使用Docker部署本地MySQL数据库 docker run --restartalways -p 3307:3306 --name mysql -e MYSOL_ROOT_PASSWORDlms123456 -d mysql:8.0.25 参数解析: 用户名:root 密码:lms123456 端口:3307 二、在Pycharm开发工具中配置连接MySQL数据库 三、安装zdppy_mysql pip inst…

《向量数据库指南》——Milvus Cloud索引增强如何提升 RAG Pipeline 效果?

索引增强 1.自动合并块 在建立索引时&#xff0c;分两个粒度搭建&#xff0c;一个是chunk本身&#xff0c;另一个是chunk所在的parent chunk。先搜索更细粒度的chunks&#xff0c;接着采用一种合并的策略——如果前k个子chunk中超过n个chunk属于同一个parent chunk&#xff0c…

centos下编译安装redis最新稳定版

一、目标 编译安装最新版的redis 二、安装步骤 1、redis官方下载页面 Downloads - Redis 2、下载最新版的redis源码包 注&#xff1a;此时的最新稳定版是 redis 7.2.5 wget https://download.redis.io/redis-stable.tar.gz 3、安装编译环境 yum install -y gcc gcc-c …

使用patch-package自动修改node_modules中的内容/打补丁

背景 在使用VuePress搭建个人博客的过程中&#xff0c;我需要使用到一个用来复制代码块的插件uepress-plugin-nuggets-style-copy。 问题&#xff1a;插件可以正常安装&#xff0c;但是启动会报错。通过查看错误信息&#xff0c;定位是插件中的copy.vue文件出现错误&#xff0c…

学习笔记——动态路由——OSPF聚合(汇总)

十一、OSPF聚合(汇总) 1、路由聚合(汇总) 路由汇总是一种重要的思想&#xff0c;在大型的项目中是必须考虑的一个重点事项。随着网络的规模越来越大&#xff0c;网络中的设备所需维护的路由表项也就会越来越多&#xff0c;路由表的规模也就会逐渐变大&#xff0c;而路由表是需…

【智能算法应用】麻雀搜索算法SSA优化Kmeans图像分割

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法&#xff08;SSA&#xff09;原理及实现 2.数学模型 Kmeans是一种无监督的聚类算法,由于参数简洁,时间复杂度低已成功应用于图像分割,取得了良好的分割效果。但传统的 K 均值聚…

45 mysql truncate 的实现

前言 truncate 是一个我们也经常会使用到的命令 其作用类似于 delete from $table; 但是 他会比 delete 块很多&#xff0c;这里我们来看一下 它的实现 delete 的时候会逐行进行处理, 打上 删除标记, 然后 由后台任务 进行数据处理 truncate table 的实现 执行 sql 如下 …

计算机图形学入门24:材质与外观

1.前言 想要得到一个漂亮准确的场景渲染效果&#xff0c;不只需要物理正确的全局照明算法&#xff0c;也要了解现实中各种物体的表面外观和在图形学中的模拟方式。而物体的外观和材质其实就是同一个意思&#xff0c;不同的材质在光照下就会表现出不同的外观&#xff0c;所以外观…

HTTP与HTTPS的主要区别

HTTP&#xff08;超文本传输协议&#xff09;与HTTPS&#xff08;超文本传输安全协议&#xff09;的主要区别在于安全性、数据传输方式、默认使用的端口以及对网站的影响。 一、安全性&#xff1a; HTTP是一种无加密的协议&#xff0c;数据在传输过程中以明文形式发送&#x…

使用myCobot280和OAK-D OpenCV DepthAI摄像头制作一个实时脸部跟踪的手机支架!

引言 由于YouTube和Netflix的出现&#xff0c;我们开始躺着看手机。然而&#xff0c;长时间用手拿着手机会让人感到疲劳。这次我们制作了一个可以在你眼前保持适当距离并调整位置的自动移动手机支架&#xff0c;让你无需用手拿着手机。请务必试试&#xff01; 准备工作 这次我们…

最新版本Anaconda 2024.06-1安装设置

最新版本Anaconda 2024.06-1安装设置 零、时光宝盒 做自己的光&#xff0c;不需要很亮 太阳很强大&#xff0c;耀眼夺目&#xff0c;给世界带来温暖和阳光。 萤火虫很弱小&#xff0c;若隐若现&#xff0c;却给黑暗中前行的人带来希望。 发光不是强者的权利&#xff0c;我们…

CTF入门知识点

CTF知识点 md5函数 <?php$a 123;echo md5($a,true); ?> 括号中true显示输出二进制 替换成false显示输出十六进制绕过 ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c&#xff0c;这个字符串前几位刚好是 or 6 而 Mysql 刚好又会把 …