是什么

接口、总线

出现的过程

  • 早期计算机中各种设备使用的总线接口是完全不一样的
    • 网卡用着网卡的接口
    • 声卡用这声卡的接口
    • 显卡用着显卡的接口
  • 接口不统一,以后升级也只能使用之前接口的外设,有很大的局限性
  • 为了解决这种状况
    • IBM 联合 intel 制定了大名鼎鼎的 ISA 总线(第一代总线)
      • 上世纪 80 年代
      • 并行总线
      • 缺点
        • 并行总线,当时的抗干扰技术不成熟,频率就无法做到很高,带宽 8 MB/s
        • 不能即插即用,需要手动去分配系统资源
        • 最大只支持 6 个外围设备
    • PCI 总线(第二代总线)
      • 132 MB/s
      • 即插即用
      • 不依赖于具体某款 CPU 的独立总线
      • 缺点
        • 依旧采用并行总线
        • 共享总线,高负载下很多设备会抢带宽
        • 不支持热拔插
    • PCIe 总线(第三代总线,gen3,3.0)

PCIe 有两个存在的形态

  • PCIe 接口
    • PCIe 插槽
      • 可以插 PCIe 接口的
        • 显卡
        • 网卡
        • 固态硬盘
        • 无线网卡
        • 有线网卡
        • 食谱采集卡
      • 还可以把 PCIe 接口转成其他接口
        • PCIe 转 M.2
        • PCIe 转 USB
        • PCIe 转 Type-c
  • PCIe 通道
    • 比方说 M.2固态硬盘虽然接口的形状是 M.2,但是数据传输依赖的是 PCIe 通道,PCIe 在这里就承担数据传输总线的作用了。因此可以简单的理解 M.2 就是一个换了形状的 PCIe 接口
    • 雷电3 也是利用 PCIe 通道来传输数据的

PCIe 的带宽

PCIe 的带宽是根据接口的长度计算的

PCIe X1、PCIe X2、PCIe X4、PCIe X8、PCIe X16

PCIe

任何 X16 的设备都可以插在尾部非闭合的 X1 槽中运行,只不过这个设备肯定是无法发挥全部的性能了。

也可以把 X1 的设备插在 X16 的槽中运行,只不过这样就浪费插槽带宽了。

残血 M.2 和 雷电3 说的就是 PCIe X2 速率的;

满血 M.2 和 雷电3 说的就是 PCIe X4 速率的。

PCIe 速率

PCIe 引脚定义

img

img

img

img

img

下面这篇文章的后半部分有对 PCIe 的改造实验,值得学习

J1900软路由加装无线网卡做AP折腾记

PCIe 没有中断引脚

名词解释

Root Complex

RC,根联合体,根复合体

学习起步

01. PCI-Express

https://blog.csdn.net/u013140088/category_7015733.html

02. PCIe基础知识

https://blog.csdn.net/zqixiao_09/article/details/51842542

03. PCIe总线基本概念

http://www.elecfans.com/d/664309.html

04. PCIe应用实战 (付费)

https://blog.csdn.net/weiaipan1314/category_9722794.html?utm_source=ffzl_BWzd

05. Linux 下的 PCIE 设备驱动设计与实现

https://max.book118.com/html/2019/1026/6201155022002120.shtm

06. Linux下PCI设备驱动开发详解

https://blog.csdn.net/weixin_42092278/article/details/81638530

07. mtk7621驱动

http://doc.okbase.net/22510743/archive/259892.html

08. rt3070 无线网卡移植

http://blog.chinaunix.net/uid-30407552-id-5182640.html

09. 无线驱动移植与开发-MTK源码分析

https://blog.csdn.net/u011212816/article/details/81137126

10. PCIe扫盲

https://blog.csdn.net/kunkliu/category_9091238.html

11. 【14】PCIe架构下memory空间、IO空间、PCIe配置空间简介

arm统一编址,x86独立编址

https://blog.csdn.net/linjiasen/article/details/87944672

12. PCIe学习笔记(一)——硬件设备识别扫盲篇(史无前例的好文章)

https://blog.csdn.net/codectq/article/details/104472150

13. Linux那些事儿 之 我是PCI

https://blog.csdn.net/fudan_abc/category_345294.html

14. 一步一步开始FPGA逻辑设计 - 高速接口之PCIe

https://blog.csdn.net/jackxu8/article/details/53288385

15. PCIe扫盲系列博文连载目录篇

(第一阶段)http://blog.chinaaet.com/justlxy/p/5100053251

(第二阶段)http://blog.chinaaet.com/justlxy/p/5100053328

(第三阶段)http://blog.chinaaet.com/justlxy/p/5100053481

(第四阶段)http://blog.chinaaet.com/justlxy/p/5100057779

(第五阶段)http://blog.chinaaet.com/justlxy/p/5100061871

link 可以有 1、2、4、8、16、32 条 lane

img

一条 lane 是由一个 transmitter 和 一个 receiver 构成

img

1.2 PCI 问题

一、flight time

二、clock skew

三、signal skew

1.3 PCIe 解决

一、数据流中包含了 clock,不需要外加 clock,所以,无论 clock 周期多小,或者无论信号需要多长的传播时间,都不存在 flight time 这个问题

二、同样,因为没有外部 clock,就没有clock skew 的问题

三、对于只有一条lane是没有signal skwe的问题,但对于多条lane,PCIe 也有解决办法

1.4 PCIe 信号线

PCIe ≈ SPI(去掉CS) + RS485

即,PCIe像SPI一样使用同步时钟线,全双工;像RS485一样使用差分信号传输。PCIe的CLK、Rx、Tx均使用差分信号传输。牛逼🐮!

1.5 PCIe 拓扑结构

pcie拓扑结构

1.6 PCIe 是点对点

4.1 PCIe 层级结构

PCIe 总线采用串行通信方式,使用数据包(Packet)进行数据传输。

在PCIe总线中,数据报文在接收和发送过程中,需要通过多个层次,包括事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical Layer)。

img

4.2 TLP

Transaction Layer Packet。

Host 与 PCIe 设备之间,数据传输都是以 Packet 形式进行的;

事务层根据上层(软件层或者应用层)请求(Request)的类型、目的地址和其它相关属性,把这些请求打包,产生TLP。

然后这些TLP往下,经历数据链路层、物理层,最终达到目标设备。

每个事物都要通过一个或者多个TLP包实现。

TLP主要由三部分组成:Header、Data、CRC 。

TLP生于发送端的事务层,终于接收端的事务层。

4.2.1 TLP Header

TLP Header 长 3、4 个 DW。

包括发送者的各种相关信息,目标地址(该TLP要给谁)、TLP类型(Memory read,Memory write等)、数据长度(如果有的话)等等。

img

第1个双字是所有类型的TLP都有的;后面的3个双字根据 Fmt、Type属性改变。

img

4.2.2 TLP Data

即 TLP 的 Payload域,用来放有效载荷数据。该域不是必须的,因为并不是每个 TLP 都必须携带数据的,比如 Memory Read TLP,它只是一个请求,数据是由目标设备通过 Completion TLP 返回的。

一个 TLP 最大载量是4KB,数据长度大于4KB的话,就需要分几个TLP传输。

4.2.3 CRC

对 Header 和 Data 生成一个 CRC。

它是可选的,可以设置不加 CRC。

4.3 PCIe 配置空间

每个 PCIe 设备,都有这么一段空间,Host 软件可以读取它获得该设备的一些信息,也可以通过它来配置该设备,这段空间就叫做 PCIe 的配置空间。

PCIe 有两种类型的配置空间,Type0 和 Type1,分别对应端点设备(Endpoint)和桥设备(Root和Switch端口中的P2P桥),我们只讨论端点设备的配置空间具体属性。

4.4 PCIe 空间

PCIe内部有配置空间、memory空间、IO空间(不讨论,暂不用)。

处理器访问这些空间都是在开机的时候提前进行了地址映射。

处理器访问配置空间获得设备的状态和设备属性信息,处理器访问memory空间可以和PCIe设备进行数据交互。

4.5 BAR寄存器和BAR空间

BAR寄存器在PCIe配置空间里面,一个PCIe设备,可能有若干个内部空间(最多6个)需要映射到主机memory地址空间,设备出厂时,这些空间的大小和属性都写在

5.1 PCIe 设备驱动

PCIe 总线上的各种外设驱动可以看作是字符设备驱动。

PCIe 设备驱动:Linux 内核的 PCIe 设备驱动 + 总线上所载设备本身的驱动。

前者不需要我们去实现,也很难实现

5.2 Intel

PCIe 由Intel提出,高速串行点对点,独享通道带宽。

5.3 /dev

/dev 中放的都是字符设备、块设备,网络设备并不属于此二类,无法实现它们的接口。 🔗

Linux设备驱动与整个软硬件系统的关系

网络设备并没有贯彻“一切皆文件“的思想,网络设备不以/dev下的设备文件为接口,用户程序通过socket作为访问硬件的接口🔗

6.1 PCI 设备驱动组成

PCI本质上就是一种总线,具体的PCI设备可以是字符设备、网络设备、USB设备等,所以PCI设备驱动应该包含两部分

  1. PCI 驱动
  2. 根据需求的设备驱动

根据需求的设备驱动是最终目的,PCI驱动只是手段帮助需求设备驱动达到最终目的而已。换句话说PCI设备驱动不仅要实现PCI驱动,还要实现根据需求的设备驱动。

PCI设备驱动

7.1 PCI 三种地址空间

PCI 设备上由三种地址空间:PCI的I/O空间、PCI的存储空间、PCI的配置空间。

CPU可以访问PCI设备上的所有地址空间。其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci ? /proc/bus/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。

7.2 驱动中的 struct

Linux 驱动程序通常使用结构体(struct)来表示一种设备,而结构体变量则代表一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,次设备号等价于结构体数组下标。

14.1 配置空间

代码

hello_pcie.c

1

// #define DMA_BUFFER_SIZE 1*1024*1024 
#define DMA_BUFFER_SIZE 512

2

/*
 * The pci_dev structure is used to describe PCI devices.
 */
struct pci_dev {
    	// 总线设备链表元素bus_list:每个 pci_dev 结构除了链接到全局设备链表中外,
    	// 还会通过这个成员链接到其所属 PCI 总线的设备链表中。
    	// 每一条 PCI 总线,都维护一条它自己的设备链表视图,以便描述所有连接在该PCI总线上的设备。
    	// 其表头由PCI总线的pci_bus结构中的 devices 成员所描述
        struct list_head bus_list;      /* node in per-bus list */	
    	// 总线指针bus:指向这个 PCI 设备所在的 PCI 总线的 pci_bus 结构。
    	// 因此对于桥设备而言,bus 指针将指向桥设备的主总线(primay bus),也即指向桥设备所在的PCI总线
        struct pci_bus  *bus;           /* bus this device is on */
    	// 指针subordinate:指向这个 PCI 设备所桥接的下级总线。
    	// 这个指针成员仅对桥接设备才有意义,而对一般非桥PCI设备而言,该指针成员总是为NULL
        struct pci_bus  *subordinate;   /* bus this device bridges to */

    	// 无类型指针 sysdata:指向一片待定于系统的扩展数据
        void            *sysdata;       /* hook for sys-specific extension */
    
    	// 指向该 PCI 设备在 /proc 文件系统中对应的目录项
        struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */
        struct pci_slot *slot;          /* Physical slot this device is in */

    	// PCI 设备的设备功能号
        unsigned int    devfn;          /* encoded device & function index */
    	// 16位无符号整数,表示 PCI 设备的厂商ID
        unsigned short  vendor;
    	// 16位无符号整数,表示 PCI 设备的设备ID
        unsigned short  device;
        unsigned short  subsystem_vendor;
        unsigned short  subsystem_device;
    	// 32位无符号整数,表示该 PCI 设备的类别,
    	// 其中,bit[7:0]为编程接口,bit[15:8]为子类别代码,bit [23:16]为基类别代码,
    	// bit[31:24]无意义。显然,class成员的低3字节刚好对应与PCI配置空间中的类代码
        unsigned int    class;          /* 3 bytes: (base,sub,prog-if) */
        u8              revision;       /* PCI revision, low byte of class word */
        u8              hdr_type;       /* PCI header type (`multi' flag masked out) */
#ifdef CONFIG_PCIEAER
        u16             aer_cap;        /* AER capability offset */
#endif
        u8              pcie_cap;       /* PCIe capability offset */
        u8              msi_cap;        /* MSI capability offset */
        u8              msix_cap;       /* MSI-X capability offset */
        u8              pcie_mpss:3;    /* PCIe Max Payload Size Supported */
        u8              rom_base_reg;   /* which config register controls the ROM */
        u8              pin;            /* which interrupt pin this device uses */
        u16             pcie_flags_reg; /* cached PCIe Capabilities Register */
        unsigned long   *dma_alias_mask;/* mask of enabled devfn aliases */

    	// 指向这个 PCI 设备所对应的驱动程序定义的 pci_driver 结构。每一个pci设备驱动程序都
    	// 必须定义它自己的 pci_driver 结构来描述它自己
        struct pci_driver *driver;      /* which driver has allocated this device */
        
    	// 用于 DMA 的总线地址掩码,一般来说,这个成员的值是 0xffffffff
    	u64             dma_mask;       /* Mask of the bits of bus address this
                                           device implements.  Normally this is
                                           0xffffffff.  You only need to change
                                           this if your device has broken DMA
                                           or supports 64-bit transfers.  */

        struct device_dma_parameters dma_parms;
    
    	// 当前操作状态
        pci_power_t     current_state;  /* Current operating state. In ACPI-speak,
                                           this is D0-D3, D0 being fully functional,
                                           and D3 being off. */
        u8              pm_cap;         /* PM capability offset */
        unsigned int    pme_support:5;  /* Bitmask of states from which PME#
                                           can be generated */
        unsigned int    pme_poll:1;     /* Poll device's PME status bit */
        unsigned int    d1_support:1;   /* Low power state D1 is supported */
        unsigned int    d2_support:1;   /* Low power state D2 is supported */
        unsigned int    no_d1d2:1;      /* D1 and D2 are forbidden */
        unsigned int    no_d3cold:1;    /* D3cold is forbidden */
        unsigned int    bridge_d3:1;    /* Allow D3 for bridge */
        unsigned int    d3cold_allowed:1;       /* D3cold is allowed by user */
        unsigned int    mmio_always_on:1;       /* disallow turning off io/mem
                                                   decoding during bar sizing */
        unsigned int    wakeup_prepared:1;
        unsigned int    runtime_d3cold:1;       /* whether go through runtime
                                                   D3cold, not set for devices
                                                   powered on/off by the
                                                   corresponding bridge */
        unsigned int    skip_bus_pm:1;  /* Internal: Skip bus-level PM */
        unsigned int    ignore_hotplug:1;       /* Ignore hotplug events */
        unsigned int    hotplug_user_indicators:1; /* SlotCtl indicators
                                                      controlled exclusively by
                                                      user sysfs */
        unsigned int    clear_retrain_link:1;   /* Need to clear Retrain Link
                                                   bit manually */
        unsigned int    d3_delay;       /* D3->D0 transition time in ms */
        unsigned int    d3cold_delay;   /* D3cold->D0 transition time in ms */

#ifdef CONFIG_PCIEASPM
        struct pcie_link_state  *link_state;    /* ASPM link state */
        unsigned int    ltr_path:1;     /* Latency Tolerance Reporting
                                           supported from root to here */
#endif

        pci_channel_state_t error_state;        /* current connectivity state */
        struct  device  dev;            /* Generic device interface */

    	// 配置空间的大小
        int             cfg_size;       /* Size of configuration space */
        /*
         * Instead of touching interrupt line and base address registers
         * directly, use the values stored here. They might be different!
         */
        unsigned int    irq;
        struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */

        bool match_driver;              /* Skip attaching driver */
        /* These fields are used by common fixups */
        unsigned int    transparent:1;  /* Subtractive decode PCI bridge */
        unsigned int    multifunction:1;/* Part of multi-function device */
        /* keep track of device state */
        unsigned int    is_added:1;
        unsigned int    is_busmaster:1; /* device is busmaster */
        unsigned int    no_msi:1;       /* device may not use msi */
        unsigned int    no_64bit_msi:1; /* device may only use 32-bit MSIs */
        unsigned int    block_cfg_access:1;     /* config space access is blocked */
        unsigned int    broken_parity_status:1; /* Device generates false positive parity */
        unsigned int    irq_reroute_variant:2;  /* device needs IRQ rerouting variant */
        unsigned int    msi_enabled:1;
        unsigned int    msix_enabled:1;
        unsigned int    ari_enabled:1;  /* ARI forwarding */
        unsigned int    ats_enabled:1;  /* Address Translation Service */
        unsigned int    pasid_enabled:1;        /* Process Address Space ID */
        unsigned int    pri_enabled:1;          /* Page Request Interface */
        unsigned int    is_managed:1;
        unsigned int    needs_freset:1; /* Dev requires fundamental reset */
        unsigned int    state_saved:1;
        unsigned int    is_physfn:1;
        unsigned int    is_virtfn:1;
        unsigned int    reset_fn:1;
        unsigned int    is_hotplug_bridge:1;
        unsigned int    is_thunderbolt:1; /* Thunderbolt controller */
        /*
         * Devices marked being untrusted are the ones that can potentially
         * execute DMA attacks and similar. They are typically connected
         * through external ports such as Thunderbolt but not limited to
         * that. When an IOMMU is enabled they should be getting full
         * mappings to make sure they cannot access arbitrary memory.
         */
        unsigned int    untrusted:1;
        unsigned int    __aer_firmware_first_valid:1;
        unsigned int    __aer_firmware_first:1;
        unsigned int    broken_intx_masking:1; /* INTx masking can't be used */
        unsigned int    io_window_1k:1; /* Intel P2P bridge 1K I/O windows */
        unsigned int    irq_managed:1;
        unsigned int    has_secondary_link:1;
        unsigned int    non_compliant_bars:1;   /* broken BARs; ignore them */
        unsigned int    is_probed:1;            /* device probing in progress */
        pci_dev_flags_t dev_flags;
        atomic_t        enable_cnt;     /* pci_enable_device has been called */

        u32             saved_config_space[16]; /* config space saved at suspend time */
        struct hlist_head saved_cap_space;
        struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
        int rom_attr_enabled;           /* has display of the rom attribute been enabled? */
        struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
        struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */

#ifdef CONFIG_PCIE_PTM
        unsigned int    ptm_root:1;
        unsigned int    ptm_enabled:1;
        u8              ptm_granularity;
#endif
#ifdef CONFIG_PCI_MSI
        const struct attribute_group **msi_irq_groups;
#endif
        struct pci_vpd *vpd;
#ifdef CONFIG_PCI_ATS
        union {
                struct pci_sriov *sriov;        /* SR-IOV capability related */
                struct pci_dev *physfn; /* the PF this VF is associated with */
        };
        u16             ats_cap;        /* ATS Capability offset */
        u8              ats_stu;        /* ATS Smallest Translation Unit */
        atomic_t        ats_ref_cnt;    /* number of VFs with ATS enabled */
#endif
#ifdef CONFIG_PCI_PRI
        u32             pri_reqs_alloc; /* Number of PRI requests allocated */
#endif
#ifdef CONFIG_PCI_PASID
        u16             pasid_features;
#endif
        phys_addr_t rom; /* Physical address of ROM if it's not from the BAR */
        size_t romlen; /* Length of ROM if it's not from the BAR */
        char *driver_override; /* Driver name to force a match */

        unsigned long priv_flags; /* Private flags for the pci driver */
};

用图说话

  1. 一个典型的33MHz的PCI总线系统图

img

  1. PCI总线是一种共享总线,所以需要仲裁器(Arbiter)来决定当前时刻的总线控制权,一般该仲裁器位于北桥中。仲裁器(主机)通过一对引脚,REQ(request)和 GNT(grant)来与各个从机连接。

img

  1. 随着版本更新,时钟频率逐渐提高,导致总线的最大负载越来越少,最后只能插一个PCI卡了。

img

  1. 每个Link支持1~32个通道(Lane)

img

  1. 一个 Lane

img

  1. PCIe总线系统的拓扑结构。

img

  1. PCI总线的地址空间分配。
    1. PCI一共支持三种地址空间:Memory Address Space、I/O Address Space、Configuration Address Space。其中x86处理器可以直接访问的只有 Memory Address Space 和 I/O Address Space。而访问 Configuration Address Space 则需要通过索引 I/O 寄存器来完成。
    2. 注:在PCIe中,则引入了一种新的Configuration Address Space 访问方式:将其直接映射到了Memory Address Space 当中。

img

  1. PCI总线具有32位数据/地址复用总线,所以其地址空间位2的32次方=4GB。也就是PCI上的所有设备共同映射到这4GB上,每个PCI设备占用唯一的一段PCI地址,以便PCI总线统一寻址。每个PCI设备通过PCI寄存器中的基地址寄存器来制定映射的首地址。PCI地址空间对应于计算机系统结构中的PCI总线。

PCI地址空间

  1. 如果处理器具有32位的地址总线,其理论可寻址空间位2的32次方=4GB。但这并不意味着内存就可以4GB大小,其实XP系统最大内存约为2GB。CPU把系统中各个设备的存储空间映射到一个统一的存储空间,成为系统存储空间共4GB,这样CPU就可以访问到所有的存储器。比如PCI存储器映射到从0xFFF80000开始的地址空间,显卡映射到0XFFF00000,再加上操作系统会占用一些空间,就只剩下不到2G能真正分配给物理内存了。

系统地址空间与PCI地址空间

  1. PCIe总线在软件上是向前兼容PCI总线的。因此PCIe总线完整的继承了PCI总线中的配置空间头(Configuration Space Header)的概念。在PCIe总线中也有两种Header,Header0和Header1,分别代表非桥设备(Endpoint)和桥设备,这与PCI总线是完全一致的。

img

  1. Root Complex 和 Switch 都是全新的PCIe概念。(注:Root Complex 经常被称为RC或者Root)
    1. 他们结构中的每一个端口(Port)都可以对应于PCI总线中的PCI-to-PCI桥的概念。
    2. 也就是说,每一个RC和Switch中一般都有多个类似于PCI-to-PCI桥的东西。分别如下两张图所示:

img

img

  1. 一个典型的服务器PCIe总线系统的拓扑结构

服务器PCIe拓扑结构

  1. 一个典型的PC的PCIe总线系统的拓扑结构

img

  1. 和很多串行传输协议一样,一个完整的PCIe体系结构包括应用层、事物层(Transaction Layer)、数据链路层(Data Link Layer)和物理层(Physical Layer)。其中,应用层并不是PCIe所规定的内容,完全由用户根据自己的需求进行设计,另外三层都是PCIe Spec明确规范的,并要求设计者严格遵循的。

    下面是一个简化的PCIe总线体系结构,其中 Device Core and interface to Transaction Layer 就是我们常说的应用层或者软件层。这一层决定了PCIe设备的类型和基础功能呢个,可以由硬件(如FPGA)或者软硬件系统实现。

    1. 如果该设备为Enpoint,则其最多可拥有8项功能(Function),且每项功能都有一个对应的配置空间(Configuration Space)。
    2. 如果该设备为Switch,则应用层需要实现包路由(Packet Routing)等相关逻辑。
    3. 如果该设备为Root,则应用层需要实现虚拟的PCIe总线0(Virtual PCIe Bus 0),并代表整个PCIe总线系统与CPU通信。

    事务层(Transaction Layer)

    1. 接收端的事物层负责事务层包(Transaction Layer Packet,TLP)的解码与校验;
    2. 发送端的事务层负责TLP的创建。
    3. 此外,事务层还有 QoS(Quality of Service)和流量控制(Flow Control)以及Transaction Ordering等功能。

    数据链路层(Data Link Layer)

    1. 负责数据链路层包(Data Link Layer Packet,DLLP)的创建、解码和校验。
    2. 同时,本层还实现了Ack/Nak的应答机制。

    物理层(Physical Layer):

    1. 物理层负责Ordered-Set Packet 的创建与解码。同时负责发送与接收所有类型的包(TLPs、DLLPs和Ordered-Sets)。
    2. 在发送之前,还需要对包进行一些列的处理,如Byte Stiping、Scramnle(扰码)和Encoder(8b/10b for Gen1 & Gen2,128b/130b for Gen3 & Gen4)。对应的,在接收端就要进行相反的处理。
    3. 此外,物理层还实现了链路训练(Link Training)和链路初始化(Link Initialization)的功能,这一般是通过链路训练状态机(Link Training and Status Machine,LTSSM)来完成的。

    需要注意的是,在PCIe体系结构中,事务层、数据链路层和物理层存在于每一个端口(Port)中,也就是说Switch中必然存在一个以上的这样的结构(包括事务层、数据链路层和物理层的)。一个简化的模型如第三幅图所示。

img

img

🔗

img

关于事务层、数据链路层和物理层的详细的功能图如下图所示:

img

  1. 在介绍事务层之前,首先简单地了解一下PCIe总线的通信机制。假设某个设备要对另一个设备进行读取数据的操作,首先这个设备(称之为Requester)需要向另一个设备发送一个Request,然后另一个设备(称之为Completer)通过Completion Packet 返回数据或者错误信息。在PCIe Spec中,规定了四种类型的请求(Request):Memory、IO、Configuration和Messages。其中前三种都是从PCI/PCI-X总线中继承过来的,第四种Messages是PCIe新增加的类型。详细如下图所示:

img

从表中我们可以发现,只有Memory Write和Message是Posted类型的,其他的都是Non-Posted类型的。所谓Non-Posted,就是Requester发送了一个包含Request的包之后,必须要得到包含Completion的包的应答,这次传输才算结束,否则会进行等待。所谓Posted,就是Requester的请求并不需要Completer通过发送包含Completion的包进行应答,当然也就不需要进行等待了。很显然,Posted类型的操作对总线的利用率(效率)要远高于Non-Posted。

那么为什么要分为Non-Posted和Posted两种类型呢?对于Memory Writes来说,对效率要求较高,因此采用了Posted的方式,但是这并不意味着Posted类型的操作完全不需要Completer进行应答,Completer仍然可采用另一种应答机制——Ack/Nak的机制(在数据链路层实现的)。

  1. PCIe的TLP包一共有以下几种类型:

img

  1. TLP传输的示意图如下图所示:

img

  1. TLP在整个PCIe包结构的位置如以下两张图所示:(第一张为发送端,第二张为接收端)

img

img

  1. 其中,TLP包结构图如下图所示:

img

图中的TLP Digest即ECRC(End-to-End CRC),是可选项。此外,TLP的长度(包括其中的Header、Data和ECRC)是以DW(双字,即四字节)为单位的。

  1. 下面聊一聊Non-Posted Transaction(包括Ordinary Read、Locked Read和IO/Configuration Writes)与Posted Writes(包括Memory Writes和Message Writes)。

    1. Non-Posted Transaction

      1. Ordinary Reads

        下图显示的是一个Endpoint向System Memory发送读请求(Read Request)的例子。

img

​ 在这个例子中,Endpoint的读请求通过了两个Switch,然后到达其目标,即Root。Root对读请求的包进行解码后,并从中识别出操作的地址,然后锁存数据,并将数据发送至Endpoint,即包含数据的Completion包,ClpD。需要注意的是,PCIe允许每个包的最大数据量(Max Data Payload)为4KB,但实际上设计者往往会采用较小的Max Payload Size(比如128,256,512,1024和2048)。因此,常常一个读请求会对应对个ClpD,即将大于Max Payload Size的数据分成多个包发送。如果遇到错误,则Root会通过Completion包告知响应的Endpoint。

​ 注:Root向发送请求的Enpoint发送Completion包,是通过Request包中的BDF信息(Bus,Device和Function)进行查找对应的Endpoint的。

  1. ​ Posted Writes

  2. Memory Writes

PCIe中的Memory写操作都是Posted的,因此Requester并不需要来自Completer的Completion。一个简单的Memory Writes例子如下

img

因为没有返回Completion,所以当发生错误时,Requester也不知道。但是,此时Completer会将错误记录到日志(Log),然后向Root发送包含错误信息的Message。

  1. PCIe总线设计之初,充分考虑到了音频和视频传输等这些对时间要求特别敏感的应用。为了保证这些特殊应用的数据包能够得到由县发送,PCIe Spec中为每一个包都分配了一个优先级,通过TLP的Header中的3位(即TC,Traffic Class)。如下图所示:

img

  1. 端口仲裁

img

  1. Flow Control DLLP

    PCIe总线中要求接收方必须经常(在特定时间)向发送方报告其VC Buffer的使用情况。方式是使用 Flow Control DDLP(数据链路层包)。

img

  1. 数据链路层不仅可以转发TLP,还可以直接向另一个相邻设备的数据链路层直接发送DLLP,比如Flow Control、Ack和Nak的DLLP。

img

  1. 我们所说的TLP和DLLP指的是包的原始发送者发的包,即TLP表示这个包的原始发送者位事务层,而DLLP则为数据链路层。但是TLP仍然会被数据链路层转发,并添加Sequence 和 LCRC。

img

物理层完成的一个重要的功能就是8b/10b编码和解码(Gen 1 & Gen2),Gen 3及之后的PCIe则采用了128b/130b的编码和解码机制。

  1. 一个Memory Read操作的例子

img

Requester:应用层(软件层)【Memory地址、事务类型(Transaction Type)、数据量(单位DW)、TC、字节使能(Byte Enable)、属性信息(Attributes)等]】–> 事务层 【 + Header 『 ID(BDF,Bus & Device & Function)』 = Mrd TLP 】-> VC Buffer ?> <- Flow Control –> 数据链路层 【 + 12位的Sequence Number、32位的LCRC = DLLP】备份 –> 物理层 【+ Start & End Characters,解字节(Strip Byte)、扰码(Scramble)、8b/10b编码并进行串行化】 –> 相邻的物理层

参考

PCI与PCIe学习一——硬件篇