上篇文章剩下兩個問題,上電掃描PCIe樹和存儲地址到PCIe地址的映射,本篇文章將對這兩個問題做出解答。本文可能會針對某一款晶片做出詳細流程解答,讀者可以只關注整個流程,具體映射機制和寄存器參考晶片datasheet。上篇文章已經了解到如何訪問配置空間,前256Bytes可以通過寄存器方式訪問,後面的256B~4k必須通過映射才能訪問,映射無非就是把配置空間映射到存儲地址空間,或者把PCIe設備空間映射到存儲地址空間。下面開始討論映射關係。
PCIe在存儲域地址空間分為三部分,PCIe控制器本身的寄存器、PCIe設備的配置空間、PCIe設備空間。寄存器和配置空間由處理器本身決定存儲地址範圍,本款處理器地址範圍如圖 1所示,配置空間地址、寄存器地址、內存地址都已經確定。PCIe設備空間需要編程人員去配置Outbound和Inbound寄存器組,確定映射關係。
圖1
Outbound在PCIe控制器中扮演的角色是將存儲地址翻譯到PCIe域的PCIe地址,Inbound是將PCIe地址翻譯成存儲地址,圖 2是一個完整的RC和EP模型地址翻譯模型,圖中的地址數字僅僅代表一種形態,具體地址應該是什麼在後文中講解。當cpu需要訪問EP的內存空間時,首先應該將存儲地址轉換成PCIe地址,在根據TLP到達指定的EP,進而將PCIe地址轉換成EP端的存儲地址。
圖2
PCIe地址到存儲地址之間的映射關係由三個寄存器決定(有兩個寄存器組應該是32個寄存器)OB_SIZE、OB_OFFSET_INDEXn、OB_OFFSETn_HI,n的範圍是0~31。在PCIe控制器中是把PCIe地址等分成32塊regions (Regions 0 to 31),每個regions的大小是可以通過編程設置OB_SIZE寄存器確定大小,大小有1, 2, 4, or 8 MB,那麼通過Outbound能夠翻譯的地址最大為8M*32=256M。存儲域地址中有5位作為識別32個regions的index,OB_SIZE的大小決定這5位在32位地址上的位置。當OB_SIZE等於0,1,2,3時,index在存儲地址中對應的位置是Bits[24:20], bits[25:21], bits[26:22], and bits[27:23],每個regions翻倍,是不是對應的地址應該按翻倍對齊呢,翻倍就是左移一位數據。OB_SIZE寄存器如圖 3所示。
圖3
OB_OFFSET_INDEXn寄存器結構如圖 4所示,n是上一段落提到的index的值。該寄存器第0位是地址翻譯使能位,第31~20位是第n個regions的基地址的31~20位,這裡的取值取決regions的大小,當OB_SIZE 等於0,1,2,3時,bits[31:20], bits[31:21], bits[31:22], and bits[31:23]位相應被使用。
OB_OFFSETn_HI寄存器的值是64位PCIe地址中第n個regions的基地址的63~32位,在32位PCIe地址中,該寄存器的值等於0。
圖4
配置OutBound翻譯的幾個寄存器也做了詳解,下面根據舉例說明。圖 5中配置空間存儲地址由CPU本身架構所決定,這部分的地址映射才晶片內部完成,不需要由編程人員配置。PCIe設備空間被分成了32等分。假設region大小是2M,PCIe地址是64位,程序中需要對0x9D3A_1234存儲地址做映射, 64位PCIe地址被使用在region 9上,初始化OBOFFSET9_HI值為0x3344 5566, OB_OFFSET9值56Ex xxxx(x的值這裡不關心,看該寄存器結構就很清楚,第0位在地址翻譯時候應該使能位1,這裡僅僅用來講解怎麼做映射,不需要關心後面的Bits) ,下面分析怎麼翻譯到PCIe地址:
由於是regions大小2M,那麼index應該取地址的bits [25:21],提取0x9D3A_1234存儲地址的bits [25:21得到01001b,該值等於9,那麼該地址應該啟用regions 9 翻譯。存儲地址的bits[20:00]是用做翻譯到PCIe地址的bits[20:00]位,該值也可以理解成reginos 9內的偏移值,值是0x001A 1234。
生成regions 塊PCIe的基地址,該地址應pcie_base=OBOFFSET9_HI <<32 + OB_OFFSET9的bits[31:21] = 0x 3344 5566 56E0 0000。
計算PCIe地址,pcie_addr = pcie_base + 存儲地址bits[20:0] =0x3344 5566 56FA 1234。
圖5
從上面存儲地址到PCIe地址映射可以看到,通過cpu尋址可以直接訪問到PCIe設備空間,最多可以訪問PCIe設備空間大小為256M,具體Outbound能夠訪問的大小根據晶片而定,當CPU與FPGA之間有大量數據交互時候也可以採用Inbound方式(Inbound地址翻譯流程如圖 6所示,這裡就不在翻譯),將CPU的內存映射到FPGA的尋址空間(這裡是站在CPU角度看的,從圖2可以理解具體映射大小還由EP決定),FPGA可以採用DMA方式訪問cpu的內存,並且速度很快。有些晶片廠商乾脆採用同核異構方式將CPU於FPGA集成在一起(有的將cpu與dsp集成在一起),兩者之間採用AXI高速總線通訊。
圖6
掃描樹的流程如下:
掃描PCIe總線樹時,需要對這些PCIe總線進行編號,即初始化PCIe橋(在本文一律指透明橋)的Primary、Secondary和Subordinate Bus寄存器。在Linux內核中採用DFS算法對PCIe總線樹進行遍歷,DFS算法是按照深度優先的原則遍歷PCIe樹,局部代碼如圖 7所示,這裡可以跟蹤pci_scan_bridge函數,函數採用DFS算法對總線進行編號(後期會講解搜索樹,比較常見hash表、紅黑樹)。
圖7
PCIe設備的IDSEL信號與PCIe總線的AD[31:0]信號的連接關係決定了該設備在這條總線上的設備號。在配置讀寫總線事務的地址周期中,AD[10:0]已經被信號已經被功能號和寄存器號使用,因此PCIe設備的IDSEL只能與AD[31:11]信號連接。上一篇文章中談到CONFIG_ADDR寄存器中的Device Number欄位一共有5位,最大能夠表示32個設備,這裡只有21位,顯然在兩者之間不能建立一一映射關係。一個PCIe總線號下最多可以掛在21個PCIe設備,那麼多個PCIe總線不就可以掛載32個設備了麼。
在32位PCIe地址空間中,PCIe設備通常將PCIe配置存放在E2PROM中,PCIe設備進行上電初始化時,將E2PROM中的信息讀到PCIe設備的配置空間作為初始值,由硬體自動完成。BAR0空間存儲了PCIe設備空間的大小,某些位被設置成不可預讀,當BAR0全部寫入1時,然後在讀取BAR0值,從數據低位看有多少連續位沒有改變。沒有改變的數據位數記錄的該PCIe設備空間的大小,假如有n位沒有改變,那麼設備空間大小應該是2的n次方。第0位代表IO/Memory、第2,3位代表32/64位地址、第4位代表是否可預取,具體位定義格式可以直接參考內核PCIe總線代碼,解析BAR函數如圖 8所示。
圖8
系統軟體根據根據設備空間大小建立存儲地址PCIe設備地址空間的映射,給PCIe設備分配的PCIe基地址寫入到BAR0,如果是64位PCIe地址,那麼BAR1是高32位地址。
寫《從cpu角度理解PCIe》文章我參考了部分晶片的datasheet,並結合linux代碼分析,本文僅僅起到分析流程的作用,具體映射機制和寄存器晶片參考相應晶片datasheet。如有錯誤,還望指正。