文章目录
-
- 1、preface
- 2、ARM统一内存控制器模型
- 3、CPU与PCI/PCIe设备的通信模型
- 4、PCI/PCIe的硬件结构体系(硬核)
-
- 1)PCI/PCIe背景介绍
- 2)PCI/PCIe规范
- 3)PCI控制器(简单了解)
- 4)PCIe控制器
- 5)PCIe设备
-
- 1、概述
- 2、PCIe设备的地址空间
-
- 1)PCIe的四种地址空间
- [2)Memory / IO地址空间](#2)Memory / IO地址空间)
- 3)Configuration地址空间(重点关注)
- 6)TLP报文
- 7)PCIe的枚举机制
- 8)PCIe系统的软件层次
- 9)PCI/PCIe中断体系
- [5、Linux PCI/PCIe 子系统](#5、Linux PCI/PCIe 子系统)
- 6、PCIe调试手法
1、preface
1)PCI/PCIe的复杂之处在与理解 硬件结构(硬件组成和硬件拓扑),软件主要工作是配置;
2)软件使用以及PCI/PCIe设备驱动编写是很简洁的! ---- 硬件越复杂,软件越简化!
3)PCIe具有多种优良的特性,集各种总线的优良特点;可灵活配置(但配置复杂性高),是其它外设所不具备的优良特性;
2、ARM统一内存控制器模型

1)ARM下操控所有的控制器(I2C、SPI、UART、PCI、EMMC),都通过内存控制器?是的
内存地址划分为三大类,每一类都有自己的内存空间:
System memory map : DDR、NOR、SRAM、DMA、ROM
ARM IP Bus(AIPS): PWM、GPIO、UART、I2C、SPDIF、ADC、USB、RNGB
Debug Access Port :CPU0 PMU、CPU0 debug、CAP ROM Table、DAP ROM Table
2)如何选中某个控制器?当地址范围落在某个控制器,CPU自动拉起对应的CS线(硬件实现),其它不受影响;
3)趋势:硬件负责更多的事情,软件越来越简单;
3、CPU与PCI/PCIe设备的通信模型
1)PCI/PCIe相对其它总线有什么优势?
先来看看内存如何访问,在bootloader下配置好DDR后,向某个地址写入数据 *addr = 0x88,即可完成访存;如此优良的特性,如何应用到其它外设?PCI/PCIe总线则是其中一种实现!
传统的总线控制器由是如何操作的呢?以I2C设备为例,我们访问I2C控制器已经实现了像内存一样配置,但访问I2C设备仍然要一系列操作才能完成(需要使用封装好的read/write方法,read/write方法里面也封装了不少操作,总之,CPU访问I2C设备 中间过程繁琐 还不容易);
而现在,配置好PCIe控制器后,PCIe设备则可以像内存那样直接访问,CPU直接发出地址访问PCIe设备,由PCIe控制器进行处理,PCIe设备将数据原路返回给CPU;而这些I2C/SPI/UART控制器无法做到;
2)如何协定地址?
PCIe控制器有固定的地址空间,PCIe设备将从这些地址空间中动态分配!
3)通信模型(类似片上高速局域网)

1、CPU发出地址,经过内存控制器选中PCI/PCIe控制器,控制器会构造TLP包通过总线发送出去,PCI/PCIe设备监听解析总线上的TLP包,如果地址落在自己的范围,便会处理TLP包的请求,处理完成后重新构造TLP包通过总线发送出去;
2、PCI/PCIe控制器与PCI/PCIe设备通过TLP包,整个过程由硬件完成,CPU是不用参与也不会感知,站在CPU的角度,CPU能像访问内存那样访问PCI/PCIe设备,而这些都是PCI/PCIe控制器的功劳!
3、站在CPU角度,通信模型如下:

4、PCI/PCIe的硬件结构体系(硬核)
1)PCI/PCIe背景介绍
1)PCI/PCIe的特点和差别
1、PCI是并行接口 - 信号线接口巨多,且高速速度受限,已逐渐给PCIe代替
2、PCIe是串行接口(差分)- 支持极高速度,与HDMI/LVDS/TypeC/MIPI一样,差分信号是高速信号实现的必备条件;
传输速率:(2.5GT/s, 5GT/s, 8GT/s, 16GT/s)
2)哪些设备使用PCI/PCIe?高速设备
1、PCI - 传统台式电脑\工业 等旧架构
2、PCIe - LAN Card、Graphics Card、WIIF芯片、SSD固态硬盘
3)PCIe是PCI的革命性升级换代产品,如今在新设计和主流消费领域,PCIe 已完全取代PCI;在嵌入式领域基本不会用到PCI(信号线巨多、高速速度还受限,还有必要了解PCI?
有必要,从硬件上,PCI是基础模型,经过优化才有PCIe,且程序上PCI/PCIe的机制并存,了解PCI 有助于更好地理解PCIe,且在阅读Linux内核源代码时能理解其中的兼容机制;
2)PCI/PCIe规范
1、PCI Express协议
- 《PCI Express Technology 3.0》,Mike Jackson, Ravi Budruk; MindShare, Inc.
- 《PCIe扫盲系列博文》,作者Felix,这是对《PCI Express Technology》的理解与翻译
- 《PCI EXPRESS体系结构导读 (王齐)》
- 《PCI Express_ Base Specification Revision 4.0 Version 0.3 ( PDFDrive )》
- 《NCB-PCI_Express_Base_5.0r1.0-2019-05-22》
2、规范有哪些内容?
1)软硬件的设计指南,让每个厂家设计出来的PCIe控制器,PCIe设备都具有通用性;
2)注意规范里涵盖PCIe的所有应用,厂家可以选择实现自己需要的必要部分(比如热拔插、P2P功能等高级功能不一定要实现);
3)作为软件,如何使用规范?
协议内容虽然庞大,作为软件开发者,大部分我们都使用不到,PCI/PCIe的协议及实现简单了解即可,可不必深入 - 芯片设计人员才需要深入研究!重点了解PCIe的原理与掌握软件控制部分(协议包、地址空间配置等);
3)PCI控制器(简单了解)
1、PCI系统硬件结构

1)PCI设备可以简单地分为PCI Bridge和PCI Agent:
- PCI Bridge:桥,用来扩展PCI设备,必定有一个Root Bridge,下面还可以有其他Bridge。
- PCI Agent:真正的PCI设备(比如网卡),是PCI树的最末端
2)如何访问PCI设备?像内存一样访问PCI设备,但要先进行较为复杂的配置
3)怎么配置PCI Agent设备?
-
选中:通过IDSEL来选中某个设备(值得注意,PCIe设备没有此IDSEL)
-
怎么访问配置空间:发起一个type 0的配置命令
PCI设备最多有8个功能,每个功能都有自己的配置空间(配置寄存器)
你要访问哪个功能?哪个寄存器?发起

-
CPU读取配置空间的BAR,得知:这个PCI设备想申请多大空间
-
CPU分配PCI地址,写到PCI设备的BAR里
4)当配置完成后,PCI设备便有了唯一的地址,后续CPU发出地址经过PCI控制器转换为TLP报文,通过总线发送出去,PCI设备(注意PCI设备也是能跑ROM程序的小系统)监听总线上的TLP报文,解析TLP报文内容,如果地址落在自己的范围,便会回应CPU,形式也是构造TLP报文原路返回;
2、PCI总线接口(数量极多)
主要分为6类:
| 类别 | 信号 | 描述 |
|---|---|---|
| 系统引脚 | CLK:给PCI设备提供时钟 RST#:用于复位PCI设备 | |
| 地址/数据引脚 | AD[31:00]:地址、数据复用 C/BE[3:0]:命令或者字节使能 PAR:校验引脚 | |
| 接口控制 | FRAME#:PCI主设备驱动此信号,表示一个传输开始了、进行中 IRDY#:Initiator ready, 传输发起者就绪,一般由PCI主设备驱动此信号 TRDY#:Target ready,目标设备驱动,表示它就绪了 STOP#:目标设备驱动,表示它想停止当前传输 LOCK#:锁定总线,独占总线,有PCI桥驱动此信号 IDSEL:Initialization Device Select,配置设备时,用来选中某个PCI设备 DEVSEL#:Device Select,PCI设备驱动此信号,表示说:我就是你想访问的设备 | |
| 仲裁引脚 | REQ#:申请使用PCI总线 GNT#:授予,表示你申请的PCI总线成功了,给你使用 | |
| 错误通知引脚 | PERR#:奇偶校验错误 SERR#:系统错误 | |
| 中断引脚(可选) | INTA#、INTB#、INTC#、INTD# |
示例:

4)PCIe控制器
1、PCIe系统硬件结构

1)拓扑关系非常多,这个是规范中理想模型,但实际应用则不会(毕竟要考虑速度,所有设备都是共享总线),只会扩展少数几个!
2)Dev Function,一个物理设备可以有多个功能(比如声卡的playback/record),也就有多个逻辑设备;
2、PCIe系统拓扑简图

1)比较PCI/PCIe
与PCI从总体上原理一样,无论从设计规范、硬件设计 到代码配置方式 都有所不同的,但是Linux内核的PCIe子系统一套代码兼容PCI/PCIe,个人认为这种打补丁的方式会导致代码的复杂度增大,且一个设备上应该不会同时存在PCI/PCIe(厂家自行打开PCI或PCIe配置),完全可以分开,毕竟PCIe有可能还会继续迭代;
2)PCI/PCIe的差异分析
1、总线接口,由于PCI的接口非常冗余,在PCIe中必须优化砍掉,并行改成串行,中断线也节省掉;
2、简化配置与拓扑方式(注意配置设计有差异,使用设备都是基于地址空间,没有差异)
1)PCI类似SPI,配置设备时使用片选引脚来选中设备,随着设备的增多,可以灵活扩展;
2)PCIe则是点对点连接(比如Root Complex中限定只有3个端口,即只能外接3个设备,且每个端口的编号固定 写死在设备配置寄存器里);使用switch来进行扩展(Switch同Root Complex),这样虽然扩展有限(实际上一个系统也不会有这么多PCIe设备挂进来,完全足够),但配置流程将大大简化;
3、三种PCIe硬件模块

1)上图中有三种PCIe硬件模块,它们都是一个pcie微控制器(有对应的ROM程序),结构类似但不完全相同(类似网络中的交换机、路由器、station的概念)
1、RC(Root Complex):结构最为复杂,功能最为强大
2、Switch:负责转发TLP - 类似交换机功能
3、Endpoint:功能和结构最为简单
2)RK3399的RC结构

4、PCIe总线接口
1)硬件连接

2)Link和Lane
1)PCIe设备的接口上,两个PCIe设备之间有一个Link,一个Link由多条Lane组成(与屏接口LVDS类似)
2)一个Link中有1对或多对"发送/接收"引脚,每对"发送/接收"引脚被称为"Lane"
3)一个Lane:有发送、接收两个方向,每个方向用2条差分信号线,所以1个Lane有4条线(上图中只有一个Lane)
4)PCIe的link宽度规范中定义支持x1, x2, x4, x8, x12, x16, x32。(上图中则是x1,厂家自行根据产品设计需要多少Lane来满足)

3、对于驱动程序
volatile unsigned int *p = addr_cpu;
unsigned int val;
*p = val; //写数据,硬件会把addr_cpu转换为addr_pci去写PCI/PCIe设备
val = *p; //读数据
5)PCIe设备
1、概述
1)PCIe设备相当于一个从机系统(也有PCIe微处理器),虽然不像主机那样复杂,但它自身也需要跟CPU交互(通过TLP包-即报文方式),也要有自己的ROM程序(解析TLP包并处理回应主机),由厂家设计并出厂烧录;
2)PCIe控制器构造复杂,且有一定数量的寄存器,这也导致PCIe成本较高,目前较为高端一点的平台才配置PCIe;
2、PCIe设备的地址空间
地址空间是真实存在的内存?除了配置空间,都是不存在的,是虚拟出来的概念,这些地址空间会被记录在主控/设备端的"结构体"中;
这些地址空间被划分为以下四类
1)PCIe的四种地址空间
1、PCIe设备四种地址空间介绍:https://zhuanlan.zhihu.com/p/649469859
2、四种地址空间,主要用到Memory/IO和Configuration空间

2)Memory / IO地址空间
Memory / IO地址空间是虚拟的,并非实际存在
1、Memory / IO空间是CPU和PCIe设备数据交互的地址空间;
2、Memory / IO空间差异,I/O可能会被外设修改数据,Memory则不会,前后读写内容一致
1)PCIe支持I/O空间,以便与需要使用I/O空间的传统设备(legacy device)兼容,即PCI或PCI-x设备。IO地址空间的大小只有4GB(32-bit)。PCIe spec并不推荐使用I/O空间,推荐使用内存空间映射(MMIO)。
3、Memory / I/O空间多大?不同设备不同,PCIe设备需要根据自己的配置来向CPU申请地址
理论上对于32位操作系统来说是4G(232),对于64位系统来说,有264的空间大小,规范中的地址映射描述如下:

3)Configuration地址空间(重点关注)
1、Configuration地址空间实际存在的内存,PCIe使用 增强配置访问机制(ECAM)来访问此区域,PCIe Enhanced Configuration Access Mechanism (ECAM)是访问PCIe配置空间的一种机制。是将PCIe的配置空间映射到MEM空间,使用MEM访问其配置空间的一种实现。
2、每个PCIe Function都有4KB的配置内存(Configuration Space)。前256 Bytes是和PCI兼容的配置空间,剩余的是PCIe扩展配置空间(Extended Configuration Space),4K内存在规范中划分如下:

3、配置空间的Header格式

1)Type 1 Header存在于所有Bridge设备中,即每个Switch和RC(主机PCIe控制器)都有1个Type 1 Header。Type 0 Header只存在于非Bridge设备中,即Endpoint设备。
2)注意有些是硬编码,比如Device ID,Vendor ID
- Vendor ID:厂家ID,PCI SIG组织给每个厂家都分配了一个独一的ID
- Device ID:厂家给自己的某类产品分配一个Device ID
- Revision ID:厂家自定义的版本号,可以认为是Device ID的延伸
- Class Code:这是只读的寄存器,它含有3个字节,用来表明设备的功能,
3)基地址(Base Address) BAR寄存器的格式

4)Header Type

PCI/PCIe桥,它的配置寄存器格式如上上图的"Type 1 Header",
PCI/PCIe设备(Endpoint),它的配置寄存器格式如上上图的"Type 0 Header",
5)对于PCI/PCIe桥,里面的由三项重要的总线号:
1、Pirmary Bus Number:上游总线号
2、Secondary Bus Number:自己的总线号
3、Subordinate Bus Number:下游总线号的最大数值
6)TLP报文
1、TLP报文由PCI/PCIe控制器构造,是PCIe主机和从机通信的基本单元,CPU可以配置控制器相应寄存器 来决定构造什么样的报文;
2、TLP报文类似网络通信包,这种方式让PCIe在通信时具有高度的自由度(协议);
1、基本格式

1)类型:你是读内存还是写内存?读IO还是写IO?读配置还是写配置?在Header里面有定义
2)地址:对于内存读写、IO读写,地址保存在Header里
3)Bus/Dev/Function/Regiser:对于配置读写,这些信息保存在Header里
4)数据:对于内存读、IO读、配置读,先发出请求,再得到数据
- 分为2个阶段:读请求报文、完成报文
- 读请求报文:不含数据
- 完成报文:含数据
2、TLP Header
1)TLP Header很重要,用来表明报文的作用

2)值得注意的是配置类型(Fmt/Type字段)

3、路由方式
所谓"路由",就是指怎么找到对方,TLP中怎么表示自己使用哪种路由?TLP头部就表明了,PCIe协议中有三种路由方式:
1)基于ID的路由
1、使用场景:配置读、配置写:使用基于ID的路由,就是使用<Bus, Device, Function>来寻找对方。配置成功后,每个PCIe设备都有自己的PCIe地址空间了。
2、两种配置类型

(1) 配置跟桥直接相连的设备,用CfgRd0类型的TLP;
(2) 配置终端设备,用CfgRd1类型的TLP;
3、基于ID路由的TLP header如下

2)基于地址的路由
1、使用场景:内存读、内存写或者IO读、IO写:
- 发出报文给对方:使用基于地址的路由
- 对方返回数据或者返回状态时:使用基于ID的路由
2、基于地址路由的TLP header如下

3)基于消息路由
1、使用场景:各类消息,比如中断、广播等:使用消息路由
2、基于消息路由的TLP header如下

7)PCIe的枚举机制
1、枚举过程
1、PCIe枚举过程介绍: https://zhuanlan.zhihu.com/p/4552982975
2、PCIe内部有探测状态机,硬件实现扫描探测;
3、PCIe的扫描机制 - 深度优先;
4、在什么时候进行枚举?在系统复位或上电之后,配置软件(例如UEFI)需要扫描PCIe的网络结构,来发现整个拓扑,并得知这个网络结构是如何填充的(硬件完成,不需要软件参与);
2、PCIe热拔插
PCIe热插拔原理详解 :https://zhuanlan.zhihu.com/p/1907368553524495794
1)PCIe热拔插实现与USB类似的功能
2)PCIe热拔插不一定需要实现(属于一种扩展功能),在嵌入式领域,一般不提供对外拔插接口,在上电之前要求全部插入;
3)PCIe热拔插软硬件要求:
1、硬件接口上使用检测引脚PRSNT1# 和 PRSNT2#来支持;
2、PCIe控制器实现Hot Plug控制器;
3、Hot Plug device驱动,Hot Plug service;
4)Hot-Plug整体框架如下

8)PCIe系统的软件层次

1)在软件的角度,我们先关注事务层(Transaction Layer),在事务层传输TLP(Transaction Layer Packet,事务层包),TLP包的构造也是由硬件实现,软件需要做的是配置PCIe控制器的寄存器,让硬件构造出符合要求的TLP包;
2)Data Link Layer 和 Physical Layer都是硬件实现;
9)PCI/PCIe中断体系
1、INTx中断机制
使用物理线路,PCI采用,与GPIO中断类似
2、MSI/MSI-X中断机制
1)MSI:Message Signaled Interrupts,基于消息中断,PCIe使用
2)PCIe控制+GICv3控制器中断模型

1、对于PCIe使用MSI/MSI-X内部机制实现非常复杂,但好在使用是比较简单,使用内核提供的以下两个函数即可完成
int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec);
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec);
5、Linux PCI/PCIe 子系统
1)回顾PCI/PCIe的整体架构

2)源码目录
PCI/PCIE源码的差别?
PCI和PCIE源码共用,准确来说 先有PCI子系统,再叠加PCIE差异部分,形成当前的架构
1、源码目录
android/kernel/fusion/4.19/drivers/pci
--controller //pci主机控制器驱动(不同SOC对应的pci/pcie控制器 驱动代码)
----dwc //DesignWare是Synopsys设计的PCIe IP核,现被多个厂商使用(比如AML、HISI)
--endpoint //pcie端点控制器驱动,用于EP模式
--hotplug //热拔插控制器驱动
--pcie //实现pcie特有的功能(AER错误报告\DPC下游端口遏制\ASPM链路电源管理等)
--switch //PCIe交换器
pci_driver
pci.c
of.c //pci设备树操作集
2、属于哪类基础驱动类型?字符设备
独一档的统一设备驱动模型(bus_register\driver_register),不属于哪种基础驱动类型
3、PCI
//Faraday Technology FTPC100 PCI Controller
android/kernel/fusion/4.19/drivers/pci/controller/pci-ftpci100.c
4、PCIe for rockchip
android/kernel/fusion/4.19/drivers/pci/controller/pcie-rockchip.c
android/kernel/fusion/4.19/drivers/pci/controller/pcie-rockchip-host.c
android/kernel/fusion/4.19/drivers/pci/controller/pcie-rockchip-ep.c
5、设备驱动例子
1)wifi_driver/aic8800_fdrv/rwnx_pci.c
pci_register_driver()
--__pci_register_driver()
----driver_register(&drv->driver)
3)NVME接口SSD(使用pcie总线通信)- 实现相对复杂!
NVME(Non-volatile memory express)协议的SSD:https://www.zhihu.com/tardis/zm/art/533322468
SSD硬盘接口:https://blog.csdn.net/shuai0845/article/details/98330290
/android/vendor/amlogic/common/kernel/common_5.4/drivers/nvme
3)数据结构
1. pci_dev
struct pci_dev {
struct list_head bus_list; // 总线设备链表节点
struct pci_bus *bus; // 所属总线
struct pci_bus *subordinate; // 子总线(如果是桥接器)
unsigned int devfn; // 设备功能号 (Device:Function)
unsigned short vendor; // 厂商ID
unsigned short device; // 设备ID
unsigned short subsystem_vendor; // 子系统厂商ID
unsigned short subsystem_device; // 子系统设备ID
unsigned int class; // 设备类别
u8 revision; // 修订版本
u8 hdr_type; // 头部类型
u8 pcie_cap; // PCIe能力寄存器偏移
u8 msi_cap; // MSI能力寄存器偏移
u8 msix_cap; // MSI-X能力寄存器偏移
struct pci_driver *driver; // 绑定的驱动
struct pci_sriov *sriov; // SR-IOV信息
struct pci_dev *physfn; // 物理功能(如果是VF)
struct resource resource[DEVICE_COUNT_RESOURCE]; //最多13个资源(6个BAR + 1个ROM + 6个SR-IOV BAR)
struct resource *rom; // ROM资源
}
2.pci_bus
struct pci_bus {
struct list_head node; // 总线链表节点
struct pci_bus *parent; // 父总线
struct list_head children; // 子总线链表
struct list_head devices; // 设备链表
struct pci_dev *self; // 桥接器设备
struct resource *resource[PCI_BRIDGE_RESOURCE_NUM]; // 桥接器资源
struct pci_ops *ops; // 配置空间访问操作
struct msi_controller *msi; // MSI控制器
unsigned char number; // 总线号
unsigned char primary; // 主总线号
char name[48]; // 总线名称
struct device dev; // 设备结构
struct bin_attribute *legacy_io; // 传统IO访问
struct bin_attribute *legacy_mem; // 传统内存访问
}
3.pci_host_bridge
struct pci_host_bridge {
struct device dev; // 设备结构
struct pci_bus *bus; // 根总线
struct list_head windows; // 资源窗口列表
void *sysdata; // 系统特定数据
struct pci_ops *ops; // 配置空间操作
unsigned char busnr; // 起始总线号
unsigned char bus_max; // 最大总线号
int (*map_irq)(const struct pci_dev *dev, u8 slot, u8 pin);
void (*release)(struct pci_host_bridge *bridge);
};
4.pcie_link_state
struct pcie_link_state {
struct pci_dev *pdev; // 上游设备
struct pci_dev *downstream; // 下游设备
struct pcie_link_state *root; // 根端口链路
struct pcie_link_state *parent; // 父链路
};
4)源码分析
1、RK3399
1)PCI驱动子系统框架

2)源码整体结构
1.设备树
1)arch/arm64/boot/dts/rk3399.dtsi
pcie0: pcie@f8000000 {
compatible = "rockchip,rk3399-pcie";
#address-cells = <3>;
#size-cells = <2>;
bus-range = <0x0 0x1f>;
...... /* 省略 */
phys = <&pcie_phy>;
phy-names = "pcie-phy";
//定义了PCIe控制器寄存器在内存中的物理地址和范围。
ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
//地址转换表,将PCI 设备的地址空间映射到CPU可访问的物理地址。
reg = <0x0 0xf8000000 0x0 0x2000000>,
<0x0 0xfd000000 0x0 0x1000000>;
...... /* 省略 */
};
2)设备树解读
* reg属性里的0xf8000000:Region 0的地址
* reg属性里的0xfd000000:PCIe控制器内部寄存器的地址
* Client Register Set:地址范围 0xFD000000~0xFD7FFFFF
* Core Register Set :地址范围 0xFD800000~0xFDFFFFFF
* ranges属性里
* 第1个0xfa000000:Region1~30的CPU地址空间首地址,用于内存读写
* 第2个0xfa000000:Region1~30的PCI地址空间首地址,用于内存读写
* 第1个0xfbe00000:Region31的CPU地址空间首地址,用于IO读写
* 第2个0xfbe00000:Region31的PCI地址空间首地址,用于IO读写
3)源码接口
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/controller/pcie-rockchip-host.c
rockchip_pcie_probe()
--rockchip_pcie_parse_host_dt(rockchip);
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/controller/pcie-rockchip.c
----rockchip_pcie_parse_dt(struct rockchip_pcie *rockchip)
------platform_get_resource_byname(...)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/of.c
------of_pci_get_max_link_speed(...)
2.host的工作
* 解析设备树,根据设备树确定:寄存器地址、CPU空间地址、PCI空间地址、中断信息
* 记录资源:CPU空间地址、PCI空间地址
* 初始化PCIe控制器本身,建立CPU地址和PCI地址的映射
* 扫描识别当前PCIe控制器下面的PCIe设备
1)probe
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/controller/pcie-rockchip-host.c
rockchip_pcie_probe()
--rockchip_pcie_host_init_port(rockchip) //Initialize hardware
--rockchip_pcie_enable_interrupts(rockchip); //打开中断
//解析设备树获得PCI host bridge的资源(CPU地址空间、PCI地址空间、大小)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/of.c
--devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, &res, &io_base);
//Address transform uint
--rockchip_pcie_cfg_atu(rockchip); //设置Region对应的寄存器
/* 设置pcie配置空间 */
----rockchip_pcie_cfg_configuration_accesses(rockchip, AXI_WRAPPER_TYPE0_CFG);
/* MEM映射: Region1~30 */
----rockchip_pcie_prog_ob_atu(rockchip, reg_no+1, AXI_WRAPPER_MEM_WRITE), 20-1, rockchip->mem_bus_addr,.)
/* IO映射: Region31 */
----rockchip_pcie_prog_ob_atu(rockchip, reg_no+1, AXI_WRAPPER_IO_WRITE), 20-1, rockchip->mem_bus_addr,.)
/* 用于消息传输: Region32 */
----rockchip_pcie_prog_ob_atu(rockchip, reg_no+1, AXI_WRAPPER_NOR_MSG), 20-1, rockchip->mem_bus_addr,.)
/* Get the I/O and memory ranges from DT */
--resource_list_for_each_entry(win, &res)
--pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
--pci_bus_add_devices(bus);
3)PCIe控制器的资源
rockchip_pcie_probe()
--LIST_HEAD(res);
--devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, &res, &io_base);
res链表中记录的资源最终会放到pci_bus->bridge->windows链表里,如下图记录:

4)扫描PCIe设备过程
1、核心: 构造pci_dev
扫描PCIe总线,对每一个PCIe桥、PCIe设备,都构造出对应的pci_dev:
- 填充pci_dev的各项成员,比如VID、PID、Class等
- 分配地址空间、写入PCIe设备
2、resource结构体(记录着PCIe的资源)
里面记录的start、end等,是基于CPU角度看待的。也就是说,如果记录的是内存地址、IO地址,那么是CPU地址,不是PCI地址。并且这些地址是物理地址,要在软件中使用它们要先执行ioremap。
3、我们要找到这4个核心代码:
- 分配pci_dev
- 读取PCIe设备的配置空间,填充pci_dev中的设备信息
- 根据PCIe设备的BAR,得知它想申请什么类型的地址、多大?
- 分配地址,写入BAR
4、关键代码分为两部分:
-
读信息、得知PCIe设备想申请多大的空间
shellrockchip_pcie_probe bus = 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_dev结构体 dev_set_name(&dev->dev, "%04x:%02x:%02x.%d",...) pci_read_bases(dev, 6, PCI_ROM_ADDRESS); //读出BAR pci_device_add(dev, bus); -
分配空间
c
rockchip_pcie_probe
pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);
__pci_bus_assign_resources
pbus_assign_resources_sorted
/* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐,资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list)
__dev_sort_resources(dev, &head);
// 分配资源
__assign_resources_sorted
//分配地址空间, 把这块空间对应的PCI地址写入PCIe设备的BAR
assign_requested_resources_sorted(head, &local_fail_head);
2、AML-AXG系列
AML的PCIe IP Core使用DWC核
1.设备树
/android/common-5.15/common/arch/arm64/boot/dts/amlogic_u/meson-axg.dtsi
pcieA: pcie@f9800000 {
compatible = "amlogic,axg-pcie", "snps,dw-pcie";
reg = <0x0 0xf9800000 0x0 0x400000>,
<0x0 0xff646000 0x0 0x2000>,
<0x0 0xf9f00000 0x0 0x100000>;
reg-names = "elbi", "cfg", "config"; //定义了PCIe控制器寄存器在内存中的物理地址和范围。
interrupts = <GIC_SPI 177 IRQ_TYPE_EDGE_RISING>;
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 179 IRQ_TYPE_EDGE_RISING>;
bus-range = <0x0 0xff>;
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
//地址转换表,将PCI 设备的地址空间映射到CPU可访问的物理地址。0x82000000表示这是一个32位可预取内存区域的映射。
ranges = <0x82000000 0 0xf9c00000 0x0 0xf9c00000 0 0x00300000>;
...
num-lanes = <1>;
phys = <&pcie_phy>;
phy-names = "pcie";
status = "disabled";
};
2.主控制器入口
/android/vendor/amlogic/common/kernel/common_5.4/drivers/pci/controller/dwc/pci-meson.c
5)PCIe设备驱动
1、PCIe设备是热拔插,无法在设备树提前配置;因此PCIe设备没有对应的设备树,设备信息存放在PCI设备的内存中;
2、当设备插入时,PCIe控制器会扫描设备,读取设备信息 -> 匹配驱动 ->调用驱动的probe函数;
3、PCIe控制器配置虽然复杂,但对于PCIe设备驱动的编写却是更为简单的,因为大部分配置工作都在主控器扫描设备时完成,设备驱动进需要补充少量的配置+使能即可使用;这也是与I2C/SPI/UART这类总线相比的主要区别;
4、PCI总线设备驱动模型

1、标准的平台设备驱动程序
static struct pci_driver rwnx_pci_drv = {
.name = KBUILD_MODNAME,
.id_table = rwnx_pci_ids,
.probe = rwnx_pci_probe,
.remove = rwnx_pci_remove
};
int rwnx_pci_register_drv(void)
{
return pci_register_driver(&rwnx_pci_drv);
}
2、上电的时候,PCIe控制器的驱动程序扫描PCIe总线,识别出设备,并构造、注册pci_dev;
如果pci_device_id匹配成功,则调用probe函数
static const struct pci_device_id rwnx_pci_ids[] = {
{PCI_DEVICE(PCI_VENDOR_ID_DINIGROUP, PCI_DEVICE_ID_DINIGROUP_DNV6_F2PCIE)},
{PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_CEVA_VIRTEX7)},
{0,}
};
3、rwnx_pci_probe
rwnx_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
--rwnx_v7_platform_init(pci_dev, &rwnx_plat);
----pci_write_config_word(pci_dev, PCI_COMMAND, pci_cmd);
----pci_write_config_byte(pci_dev, PCI_CACHE_LINE_SIZE, L1_CACHE_BYTES >> 2);
----pci_request_regions(pci_dev, KBUILD_MODNAME); //申请资源
----pci_enable_msi(pci_dev); //使能msi中断
----pci_enable_device(pci_dev) //使能设备
6、PCIe调试手法
1、查看设备配置空间(与usb类似)
lspci -x # 十六进制转储
lspci -xxx # 扩展配置空间
lspci -vvv # 详细信息
2、查看特定寄存器
setpci -s 01:00.0 COMMAND=0x0000 # 禁用设备
3、启用PCI调试
echo 1 > /sys/module/pci/parameters/debug
4、查看PCI总线信息
cat /proc/bus/pci/devices
5、查看设备资源
ls -l /sys/bus/pci/devices/*/resource*