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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32-I2C硬件外设

本博文建议与我上一篇I2C 通信协议​​​​​​共同理解 合成一套关于I2C软硬件体系 STM32内部集成了硬件I2C收发电路&#xff0c;可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能&#xff0c;减轻CPU的负担 特点&#xff1a; 多主机功能&#x…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

【中项第三版】系统集成项目管理工程师 | 第 9 章 项目管理概论③ | 9.6 - 9.10

前言 第 9 章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节理论性较强&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 9.6 项目管理过程组 9.7 项目管理原则 9.8 项目管理知识领域 9.9 价值交付系统 9.10 本章练习 9.6 项目管理过程组 项目…

2024小学生古诗文大会3个多月备考:吃透真题和知识点(持续)

根据主办方发布的方案通知&#xff0c;2024年上海市小学生古诗文大会的初赛将于10月19日&#xff08;星期六&#xff09;正式开始&#xff0c;还有3个多月的时间。那么该如何有效备考2024年小学生古诗文大会呢&#xff1f;建议围绕真题拓展知识&#xff0c;举一反三。 今天我们…

UE5 视频播放(自动播放和自动清除MediaTexture)

媒体播放器的打开时播放和媒体纹理的自动清除 。 在UE5开发视频播放时&#xff0c;遇到了闪帧的现象。合理选择这两个功能可解决。

掌握 Postman 脚本:入门指南

在探索 API 测试自动化环墁下&#xff0c;Postman 脚本显现其强大功能和灵活性&#xff0c;它不仅仅是 API 测试的工具&#xff0c;更是一个综合性的自动化平台。 Postman 脚本简介 Postman 允许用户在 API 请求生命周期中运行 JavaScript 脚本&#xff0c;这些脚本分为以下三…

Vite: 近几个版本的更新

概述 在 2021 年 2 月&#xff0c;尤大正式推出了 Vite 2.0 版本&#xff0c;可以说是 Vite 的一个重要转折点&#xff0c;自此之后 Vite 的用户量发生了非常迅速的增长&#xff0c;很快达到了每周 100 万的 npm 下载量。同时&#xff0c;Vite 的社区也越来越活跃&#xff0c;…

JAVA基础知识(下)

一、String相关面试题 1. 为什么 String 在 java 中是不可变的? - 如果不是不可变的&#xff1a;这种情况根本不可能&#xff0c;因为在字符串池的情况下&#xff0c;一个字符串对象/文字&#xff0c;例如 “Test” 已被许多参考变量引用&#xff0c; 因此如果其中任何一个更…

UCOS-III 任务同步机制-信号量

1. 信号量类型 1.1 二值信号量&#xff08;Binary Semaphores&#xff09; 二值信号量只有两个状态&#xff1a;可用&#xff08;1&#xff09;和不可用&#xff08;0&#xff09;。它主要用于任务之间的互斥访问或者事件通知。例如&#xff0c;当一个任务完成某个操作后&am…

进程、程序、应用程序之间的关系

文章目录 进程和程序进程和应用程序总结参考资料 进程和程序 程序&#xff1a;程序是存放在硬盘中的可执行文件&#xff0c;主要包括代码指令和数据。程序本身是一个静态的文件&#xff0c;只有在被操作系统加载到内存中并执行时才会变成进程。 进程&#xff1a;进程是程序在…

QoS-基于queue-profile部署流量整形

拓扑图 配置 完成前面的配置QoS-基于queue-profile拥塞避免-CSDN博客 为查看实验效果&#xff0c;cir设置为1KB qos queue-profile queue_pro1queue 0 gts cir 8 # 在AR1上ping 3.3.3.3&#xff0c;10个包&#xff0c;大小600字节&#xff0c;间隔0.1s AR2查看丢弃情况&…

redis 如何使用 scan, go语言

建议用方案乙 文章目录 场景方案方案甲方案乙 拓展 场景 redis 中存在大量 key。 其中有一部分是用户登陆的 session_id&#xff0c; 结构是 &#xff1a; session_id:1session_id:2session_id:3需求&#xff1a; 有多少用户在线 方案 方案甲 keys session_id:*这种方式简…

白牌产品的数字化品牌蜕变之路

​在如今这个竞争白热化的市场中&#xff0c;品牌就是企业克敌制胜的法宝。而白牌产品&#xff0c;因缺少响亮的品牌名号和独特的品牌形象&#xff0c;常常在市场的角逐中处于下风。 不过&#xff0c;数字化时代的来临&#xff0c;却为白牌产品带来了新的曙光&#xff01; 蚓链…

学习笔记——动态路由——OSPF链路状态通告(LSA)

十、OSPF链路状态通告(LSA) 1、链路状态通告简介 (1)LAS概述 链路状态通告(Link State Advertisement&#xff0c;LSA)是路由器之间链路状态信息的载体。LSA是LSDB的最小组成单位&#xff0c;LSDB由一条条LSA构成的。是OSPF中计算路由的重要依据。 LSA用于向其它邻接OSPF路…

# Sharding-JDBC从入门到精通(6)-- Sharding-JDBC 水平分库 和 垂直分库。

Sharding-JDBC从入门到精通&#xff08;6&#xff09;-- Sharding-JDBC 水平分库 和 垂直分库。 一、Sharding-JDBC 水平分库-分片策略配置 1、分库策略定义方式如下 # 分库策略&#xff0c;如何将一个逻辑表映射到多个数据源 spring.shardingsphere.sharding.tables.<逻…

用MySQL+node+vue做一个学生信息管理系统(二):创建MySQL数据表、创建HTML用户列表页面

MySQL代码 CREATE DATABASE students;USE students;CREATE TABLE student( id INT COMMENT 学号, name VARCHAR(32) COMMENT 姓名, sex VARCHAR(8) COMMENT 性别, class VARCHAR(64) COMMENT 班级 )SHOW TABLES;下面介绍一下Vue框架的element-ui的使用方法&#xff0c;这里就不…

【第21章】MyBatis-Plus多数据源支持

文章目录 前言一、dynamic-datasource1. 特性2. 约定3. 使用方法3.1 引入依赖3.2 配置数据源3.3 使用 DS 切换数据源 二、mybatis-mate1.特性2.使用方法2.1 配置数据源2.2 使用 Sharding 切换数据源2.3 切换指定数据库节点 三、实战1. 引入库2. 配置3. 使用 DS 切换数据源4. 测…

秋招突击——7/5——复习{}——新作{跳跃游戏II、划分字母区间、数组中的第K个大的元素(模板题,重要)、前K个高频元素}

文章目录 引言正文贪心——45 跳跃游戏II个人实现参考实现 划分字母区间个人实现 参考实现数组中的第K个最大元素个人实现参考做法 前K个高频元素个人实现参考实现 总结 引言 今天就开始的蛮早的&#xff0c;现在是九点多&#xff0c;刚好开始做算法&#xff0c;今天有希望能够…

封锁-封锁模式(共享锁、排他锁)、封锁协议(两阶段封锁协议)

一、引言 1、封锁技术是目前大多数商用DBMS采用的并发控制技术&#xff0c;封锁技术通过在数据库对象上维护锁来实现并发事务非串行调度的冲突可串行化 2、基于锁的并发控制的基本思想是&#xff1a; 当一个事务对需要访问的数据库对象&#xff0c;例如关系、元组等进行操作…

RocketMQ-订阅一致及解决方案

背景 这里借用Rocketmq官方的一句话来描述订阅关系一致: 订阅关系一致指的是同一个消费者分组Group ID下&#xff0c;所有Consumer实例所订阅的Topic和Tag必须完全一致。如果订阅关系不一致&#xff0c;可能导致消息消费逻辑混乱&#xff0c;消息被重复消费或遗漏。 具体的问题…