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.rhkb.cn/news/369843.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

尚硅谷k8s 2

p54-56 k8s核心实战 service服务发现 Service:将一组 Pods 公开为网络服务的抽象方法。 #暴露Deploy,暴露deploy会出现在svc kubectl expose deployment my-dep --port8000 --target-port80#使用标签检索Pod kubectl get pod -l appmy-depapiVersion: v1 kind: Service metad…

Python酷库之旅-第三方库Pandas(006)

目录 一、用法精讲 10、pandas.DataFrame.to_excel函数 10-1、语法 10-2、参数 10-3、功能 10-4、返回值 10-5、说明 10-6、用法 10-6-1、数据准备 10-6-2、代码示例 10-6-3、结果输出 11、pandas.ExcelFile类 11-1、语法 11-2、参数 11-3、功能 11-4、返回值 …

您的私人办公室!-----ONLYOFFICE8.1版本的桌面编辑器测评

随时随地创建并编辑文档&#xff0c;还可就其进行协作 ONLYOFFICE 文档是一款强大的在线编辑器&#xff0c;为您使用的平台提供文本文档、电子表格、演示文稿、表单和 PDF 编辑工具。 网页地址链接&#xff1a; https://www.onlyoffice.com/zh/office-suite.aspxhttps://www…

Zynq系列FPGA实现SDI视频编解码,基于GTX高速接口,提供5套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案在Xilinx--Kintex系列FPGA上的应用 3、详细设计方案设计原理框图SDI 输入设备Gv8601a 均衡器GTX 解串与串化SMPTE SD/HD/3G SDI IP核BT1120转RGB图像缓存视频读取控制HDMI输出RGB转BT1120Gv8500 驱…

Docker 运行Nacos无法访问地址解决方法

参考我的上一篇文章去配置好镜像加速器&#xff0c;镜像加速器不是配置越多越好&#xff0c;重试次数多了会失败 Dockerhub无法拉取镜像配置阿里镜像加速器-CSDN博客 错误的尝试 最开始按照网上的方式去配了一大堆&#xff0c;发现下不下来。 镜像源地址&#xff1a;https:…

Kafka-服务端-副本同步-源码流程

杂 在0.9.0.0之前&#xff0c;Kafka提供了replica lag.max.messages 来控制follower副本最多落后leader副本的消息数量&#xff0c;follower 相对于leader 落后当超过这个数量的时候就判定该follower是失效的&#xff0c;就会踢出ISR&#xff0c;这里的指的是具体的LEO值。 对…

工程文件参考——CubeMX+LL库+SPI主机 阻塞式通用库

文章目录 前言CubeMX配置SPI驱动实现spi_driver.hspi_driver.c 额外的接口补充 前言 SPI&#xff0c;想了很久没想明白其DMA或者IT比较好用的方法&#xff0c;可能之后也会写一个 我个人使用场景大数据流不多&#xff0c;如果是大批量数据交互自然是DMA更好用&#xff0c;但考…

如何摆脱反爬虫机制?

在网站设计时&#xff0c;为了保证服务器的稳定运行&#xff0c;防止非法数据访问&#xff0c;通常会引入反爬虫机制。一般来说&#xff0c;网站的反爬虫机制包括以下几种&#xff1a; 1. CAPTCHA&#xff1a;网站可能会向用户显示CAPTCHA&#xff0c;要求他们在访问网站或执行…

华为实训案例

案例下载 案例内包含空拓扑图、配置完整的拓扑、以及步骤脚本文档&#xff0c;可按需下载。 拓扑图 任务清单 &#xff08;一&#xff09;基础配置 根据附录1拓扑图、附录2地址规划表、附录3设备编号表&#xff0c;配置设备接口及主机名信息。 将所有终端超时时间设置为永不…

【nvm】如何使用nvm优雅的管理Node.js

希望文章能给到你启发和灵感&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏 支持一下博主吧&#xff5e; 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境 二、什么是nvm?2.1 概念2.1 安装2.1.1 对于Mac系统2.1.2 对于Windows系统2.1.3 对于…

逻辑这回事(八)---- 时钟与复位

时钟设计总结 时钟和复位是FPGA设计的基础&#xff0c;本章总结了一些逻辑时钟复位设计、使用中出现的问题&#xff0c;给出了设计要点&#xff0c;避免后续问题重犯。时钟和复位&#xff0c;本文都先从板级谈起&#xff0c;再到FPGA芯片级&#xff0c;最后到模块级别。仅在此…

基于单片机的粉尘检测报警防护系统研究

摘要 &#xff1a; 粉尘检测是环境保护的重要环节&#xff0c;传统的粉尘检测防护系统的预防方式较为单一。本文设计了一种基于单片机的粉尘检测报警防护系统&#xff0c;能有效地检测粉尘浓度&#xff0c;进行多种方式的报警防护&#xff0c;以保证工作人员的生命健康和安全。…

软件设计之Java入门视频(11)

软件设计之Java入门视频(11) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

Floyd判圈算法——环形链表(C++)

Floyd判圈算法(Floyd Cycle Detection Algorithm)&#xff0c;又称龟兔赛跑算法(Tortoise and Hare Algorithm)&#xff0c;是一个可以在有限状态机、迭代函数或者链表上判断是否存在环&#xff0c;求出该环的起点与长度的算法。 …

汽车信息安全--欧盟汽车法规

目录 General regulation 信息安全法规 R155《网络安全及网络安全管理系统》解析 R156《软件升级与软件升级管理系统》解析 General regulation 欧洲的汽车行业受到一系列法律法规的约束&#xff0c;包括 各个方面包括&#xff1a; 1.安全要求&#xff1a;《通用安全条例&a…

基于uniapp(vue3)H5附件上传组件,可限制文件大小

代码&#xff1a; <template><view class"upload-file"><text>最多上传5份附件&#xff0c;需小于50M</text><view class"" click"selectFile">上传</view></view><view class"list" v…

Halcon OCR字符识别(极坐标转换,字符识别)

Halcon OCR字符识别&#xff08;极坐标转换&#xff0c;字符识别&#xff09; 代码 * 1.加载图片 *************************************************** dev_close_window () read_image (Image, ./img) get_image_size (Image, Width, Height) dev_get_window (WindowHandle…

PyCharm

一、介绍 PyCharm 是 JetBrains 公司开发的一款功能强大的 Python 集成开发环境&#xff08;IDE&#xff09;。它专为 Python 开发设计&#xff0c;提供了一系列强大的工具和功能&#xff0c;帮助开发者更高效地编写、调试和维护 Python 代码。以下是对 PyCharm 的详细介绍&am…

MySQL之备份与恢复(六)

备份与恢复 文件系统快照 先决条件和配置 创建一个快照的消耗几乎微不足道&#xff0c;但还是需要确保系统配置可以让你获取在备份瞬间的所有需要的文件的一致性副本。首先&#xff0c;确保系统满足下面这些条件。 1.所有的InnoDB文件(InnoDB的表空间文件和InnoDB的事务日志…

数据结构——(双)链表

文章目录 1. 定义 2. 双链表和单链表的区别 3. 代码示例 3.1 双链表节点和结构定义 3.2 初始化双链表 3.3 返回双链表的长度 3.4 在指定位置插入元素 3.5 在末尾插入元素 3.6 删除指定位置的元素并返回被删除的元素 3.7 删除末尾元素 3.8 获取指定位置的元素 3.9 修…