---
# System prepended metadata

title: 'PCI/PCIe(1): 基礎篇'
tags: [' System Software', I/O, PCIe]

---

---
tags: I/O, System Software
---

# PCI/PCIe(1): 基礎篇

## Introduction

### PCI
[Peripheral Component Interconnect(PCI)](https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect) 是一種連接電腦主機板和外部裝置的匯流排標準。其取代了 [ISA](https://en.wikipedia.org/wiki/Industry_Standard_Architecture) 和 [VESA](https://en.wikipedia.org/wiki/Video_Electronics_Standards_Association)，成為現代電腦中常見的標準。

特性上，PCI 是採同步傳輸(Synchronous transaction)。這意味著 PCI bus 只使用一個 clock。預設情況下，時脈運行在 33MHz，但允許更低到甚至 0MHz 以省電。如果硬體支援，則可以運行較高的 66MHz。

PCI 是 32 位元匯流排，採用 BURST 傳輸。所謂 BURST，意指傳輸的方式為:
* 開始傳輸
* 第一個 clock cycle 用於指定 32 位元位址
* 之後的許多 clock cycles 用來傳遞資料
* 結束傳輸

在 BURST 傳輸下，每次傳遞資料時不重新指定位址，位址會在每個資料的 clock cycle 自動遞增。如果要改變位址，需停止目前的傳輸並重啟另一個新的。

PCI 的另一關鍵特性是隨插即用[("plug-and-play")](https://en.wikipedia.org/wiki/Plug_and_play)。Host 有能力確定 PCI bus 中每個 PCI 裝置的身份，例如製造商、功能等。也可以判斷每個 PCI 裝置需要多少記憶體空間、多少中斷等資訊。除此之外，可以重新分配/映射每個裝置的記憶體空間。

### PCIe

[Peripheral Component Interconnect Express(PCIe)](https://zh.wikipedia.org/zh-tw/PCI_Express)，是電腦匯流排(bus)的一個重要分支。沿用既有的 PCI 概念及訊號標準，並且構建了更加高速的序列通訊(Serial communication)系統標準。目前這一標準由 PCI-SIG 組織制定和維護。PCIe 僅應用於內部互連。由於 PCIe 是基於既有的 PCI 系統，所以只需修改實體層而無須修改軟體就可將現有 PCI 系統轉換為 PCIe。


PCIe 標準的演進相當快速，也反映了該規格在計算機中重要的地位。與 PCI 是 shared bus 不同，PCIe 是點對點(point-to-point)的匯流排。這意味著每個裝置都有其專屬的連接，不像PCI 那樣共享 bandwidth。也因此與 33MHz 的 PCI 相比，GEN 1 的 PCIe 就可達 2.5GHz 的速度。

![image](https://hackmd.io/_uploads/SyGNkAmFC.png)
> [PCIe_vs_PCI](https://commons.wikimedia.org/wiki/File:PCIe_vs_PCI.gif)


截至撰文為止，各代的速度如下表所顯示:
![image](https://hackmd.io/_uploads/Bykt3IAjp.png)
> [PCI_Express](https://zh.wikipedia.org/zh-tw/PCI_Express)

## PCIe Connector

下圖展示了一個 1 lane 的 PCIe 的連結器(connector)。可以看到共有 36 個 Pin 腳，又分成上下個 18 個成為兩列。其中只有 6 個 pin 與傳輸資料有關(其他是電源或輔助信號)。分別是:
* 一對 REFCLK
* 一對 PER，與資料接收有關(receive)
* 一對 PET，與資料發送有關(transmit)

![image](https://hackmd.io/_uploads/BJP4_TQF0.png) ![image](https://hackmd.io/_uploads/HyJSupmtA.png)
> [fpga4fun: PCI Express - Connector](https://www.fpga4fun.com/PCI-Express1.html)

這些成對的訊號通稱為「[差分訊號(differential signal)](https://en.wikipedia.org/wiki/Differential_signalling)」。因為對中的兩個信號攜帶實際上攜帶相同的資料，但相位相反。其目的主要是為了傳輸的可靠性。

PCIe 可以通過使用多個 lane 來提升速度。由於 REFCLK pair 不需要重複，因此以具有 2 個 lane 的 PCIe 為例，需要使用 1 對 REFCLK + 2 對 PET + 2 對 PER。


## PCIe Topology

如下圖是一個 [PCI Express(PCIe)](https://en.wikipedia.org/wiki/PCI_Express) topology:

![image](https://hackmd.io/_uploads/B16DewesT.png)
> [PCI-SIG specifications](https://pcisig.com/specifications)

在 PCIe 系統中，有幾個關鍵名詞/角色:
* Hierarchy 是含 Root Complex 的 port 上，以及通過 Switch、Bridge 關聯至 RC 上的所有裝置形成的網路，如上圖所呈現即是一個 Hierachy
* [Root Complex(RC)](https://en.wikipedia.org/wiki/Root_complex) 是整個 I/O 架構的根節點，其將 CPU 和 memory subsystem 連接到 Switch 或 Endpoint，以構成 PCIe 的 Hierarchy
* PCIe to PCI/PCI-X Bridge 提供 PCIe Hierarchy 和 PCI/PCI-X Hierarchy 之間的連結
* Switch 是多個虛擬 PCI-PCI bridge 的邏輯組合
* Endpoint 在 topology 中是指一種 function，可以是 PCIe 任務的 requester 或 completer，又可分三種
    * Legacy
    * PCIe
    * RCiEP


有了連接的機制，CPU 啟動後，Host 會試著去讀不同的 bus 和 device numbers 之組合下的 function 0 之 VID/DID(DFS)，若成功讀取就代表這個 BDF 下具有 device，這個流程被稱作 **Bus Enumeration/PCI Enumeration**。

![](https://i.imgur.com/h240Shj.png =600x)

從 bus -> device -> function 的 **BDF** 結構構成了每個 PCIe 設備節點的 identifier。


:::info

關於硬體相關基礎知識，由於非專案因此不多深入。更多詳細知識推薦閱讀以下文章:
> [深入PCI与PCIe之一：硬件篇](https://zhuanlan.zhihu.com/p/26172972)
:::


## PCI Configuration Space

PCI Configuration Space 和一般記憶體空間是分離的，通常我們會以 BDF 做為 PCI 裝置的唯一位址，並以此透過晶片廠商提供的方式存取 PCI Configuration Space。以 Intel 的晶片組為例，其使用 I/O 空間的 0xCF8 來設定 Configuration Address Register 來決定要存取的 BDF(參照下圖)，然後再以 0xCFC 位址來讀取或寫入 Configuration Data Register 以得到想存取的 Configuration Space 內容。

![image](https://hackmd.io/_uploads/r14b--Aqp.png)
> [[BIOS]PCI Read/Write](https://medium.com/@jacksonchen_43335/bios-pci-read-write-14356b013c43)

傳統的 PCI 裝置的 Configuration Space 包含了 64 bytes 的 header space 和 192 bytes 的 capability space，加起來是 256 bytes。CPU 可以透過 BDF 加上 register offset 的方式來存取 I/O address space 讀取 PCI header。

![](https://i.imgur.com/jTexQz3.png)

而對 PCIe 而言，Configuration Space 擴展至 4096 bytes。剩餘的 (4096 - 256) bytes 必須以 Memory map I/O 進行存取。硬體上，因為有 root complex，如果 CPU 想讀 PCIe device 的數據，可以先讓 root complex 把數據從 PCIe device 讀到 CPU memory，然後 CPU 再從 memory 讀取數據即可;反之，如果 CPU 要往 device 寫數據，先把數據在 memory 中寫入，然後再通過 root complex 寫入 PCIe 即可。

包含裝置上的 RAM 和 ROM 也可能可以透過 MMIO 的方式進行操作。總結來說，即下圖所示:

![](https://i.imgur.com/CP5PSCc.png)

![](https://i.imgur.com/y6XDvyc.png)

下面我們特別說明幾個重要的 configuration 欄位:
* CMD (0x4): 一般情形下，Host會在 init controller 時將 BME 跟 MSE bit 設為 1
    * BME : 允許 controller 能夠向 Host 發送 DMA Read/Write Memory request
    * MSE : 控制能不能存取 controller register
* CC(class code, 0x9): 可用來判別 controller 類型為 NVMe
* BAR: 透過讀寫 BARs 可以得知目標 function 需要的記憶體空間大小，並可藉其提供配置空間位址的資訊
    * BAR0: – Memory Register Base Address, lower 32-bits 
    * BAR1: – Memory Register Base Address, upper 32-bits 
* capability pointer(0x34): linked capability



那麼 MMIO 的映射位址該如何得知呢?

![](https://i.imgur.com/ujl2phH.png)

該 mapping 的位置需透過過 PCI header 的 BAR0 和 BAR1 取得。當 Host 準備配置空間時，對 BAR register 發送 write request，嘗試將所有 bit 寫為 1，PCIe 收到 request 後，因為 BAR 有些 bit 是 read only 的，我們可以從保持不變的值取得所需空間的信息。以上圖為例，寫完後
低 12 bits 沒改變，表示需要 mapping 的空間大小是 4KB，此外注意到低 4 bits 表示了該存儲空間的一些屬性。

## PCIe Signaling

### Reapter

隨著 PCIe 的演進，傳輸速度也不斷提升，加上現代硬體設計更加複雜。在實際的硬體應用上，訊號的完整性問題成為一大問題。

為克服上述的訊號問題，可以在 CPU 和 Endpoint 之間加上 Repeater 以解決。Reapter 能做為一種訊號調節的裝置，以確保 Root complex 和 endpoint 之間的溝通品質。Repeater 又可以分為兩大類: Redriver 和 Retimer:
* Redriver: 主要用於訊號增強，能夠增強 PCIe 訊號的強度和穩定性，補償訊號衰減，常用於較短距離的連接，如主機板內部
* Retimer: 不僅可以增強訊號，還能重整訊號、提供時脈並復原數據等功能。可以在長距離連接中恢復和補償傳輸時的時序失真，確保高資料率傳輸的品質

詳細可閱讀:
> [車用PCIe中繼IC：訊號的穩定高速之旅](https://phisonblog.com/zh-tw/the-automotive-pcie-repeater-a-high-speed-journey-of-stable-signals/)

### Signal
在 PCIe 的傳輸上，訊號協議如下圖所呈現。

![image](https://hackmd.io/_uploads/ByYx7XWgyg.png)
> [從硬體的角度，解析 PCIe 介面初始化](https://www.eagletek.com.tw/post/analyzes-pcie-interface-initialization-from-a-hardware-perspective)

除了傳輸資料(DATA Rx/Tx)的訊號以外，REFCLK 即參考時脈(reference clock)，是 PCIe 可以傳輸資料的前提。PCIe 採用 100MHz 的 reference clock 以使在鏈結上的 PCIe 裝置可以高速的交換資料。

此外，有三個重要的控制訊號:
* PERST#: PERST# 被稱基礎復位(fundamental reset)，該訊號要一直被拉低直到 power rail 和 reference clock 穩定，當鏈路初始化的開始，便由低電位轉為高電位，通常這也就意外著鏈路的初始化(Link Initialization)完成
* CLKREQ#: 與低功耗狀態的轉換有關。這是一個 active-low 並用來請求 reference clock 的訊號
* WAKE#: 與低功耗狀態的轉換有關。同樣是一個 active-low 訊號，用於將 PCIe 裝置從低功耗模式重置到運作(active)模式

### Link Training

當鏈路上的所有裝置獲得電源與參考時脈後，PCIe 中的實體層會進行鏈路初始化（Link Initialization & Training）。

細節可參考 [PCIe Link Training Overview](https://www.ti.com/lit/an/snla415/snla415.pdf) 一文即影片。

## 驅動程式介面

有關描述 PCI driver 的結構，以及如何註冊 PCI driver 的詳細說明，可參考以下資料:
* [How To Write Linux PCI Drivers: pci_register_driver() call](https://docs.kernel.org/PCI/pci.html#pci-register-driver-call)


## Reference

* [深入PCI与PCIe之一：硬件篇](https://zhuanlan.zhihu.com/p/26172972)
* [深入PCI与PCIe之二：软件篇](https://zhuanlan.zhihu.com/p/26244141)
* [原來 PCIe 技術原理這麼簡單！](https://www.gushiciku.cn/dc_tw/110022319)
* [老男孩读PCIe之六：配置和地址空间](http://www.ssdfans.com/?p=8210)
* [PCIe（一） —— 基础概念与设备树](https://r12f.com/posts/pcie-1-basics/)
* [PCI Bus Subsystem](https://docs.kernel.org/PCI/index.html)
* [Linux makes your PCIe topology visible in sysfs (/sys)](https://utcc.utoronto.ca/~cks/space/blog/linux/PCIeTopologyInSysfs)
* [Fun and Easy PCIE - How the PCI Express Protocol works](https://www.youtube.com/watch?v=sRx2YLzBIqk)
* [fpga4fun - PCI](https://www.fpga4fun.com/PCI.html)
* [fpga4fun - PCIe](https://www.fpga4fun.com/PCI-Express.html)
