--- tags: I/O, System Software --- # PCI/PCIe(1): 基礎篇 ## Introduction [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 標準的演進相當快速,也反映了該規格在計算機中重要的地位。截至撰文為止,各代的速度如下表所顯示: ![image](https://hackmd.io/_uploads/Bykt3IAjp.png) > [PCI_Express](https://zh.wikipedia.org/zh-tw/PCI_Express) ### Hardware Basic 如下圖是一個 [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 系統中,有幾個關鍵角色: * [Root Complex(RC)](https://en.wikipedia.org/wiki/Root_complex) 是整個 I/O 架構的根節點,其將 CPU 和 memory subsystem 連接到 Switch 或 Endpoint,以構成 PCIe 的架構樹 * Switch 是多個虛擬 PCI-PCI bridge 的邏輯組合 * Endpoint 在 topology 中是指一種 function,可以是 PCIe 任務的 requester 或 completer。 有了連接的機制,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) ::: ### 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 表示了該存儲空間的一些屬性。 ## 驅動程式介面 有關描述 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)