【轉】PCI學習筆記


1.PCI設備編號

    每一個PCI device都有其unique PFA(PCI Fcntion Address)
    PFA由 bus number、device number、function number組成。
    
    一條PCI總線支持256個PFA,即支持256個PCI device。
    
    每個PCI芯片都有自己的device number(取決於IDSEL管腳),每個PCI芯片占用8個PFA。
    每個PCI芯片的第一個PCI device的PFA必為8的倍數。
    若PCI device的配置空間中PCI_HEADER_TYPE寄存器的最高bit為1,說明此芯片還有其他PFA,即還有其他device,即當前芯片是multi-function device.
    Each function on a multi-function device has its own configuration space。
 
   在系統中,每個PCI芯片上的所獨有的信號線是:INTA、INTB、INTC、INTD、IDSEL
   每個芯片上的IDSEL需要連到PCI總線中AD[31:11]中的一根,這對應於PCI device PFA的device number

   IDSEL is equivalent to chip select on the CPU side during address phase of  CFG RD\WR command.
   DEVSEL is used in every transaction when the target claims the cycle address to it. It is like saying this cycle in mine, and nobody touches it!

2.PCI配置空間

    每個PCI邏輯設備都有自己的配置空間,里面存儲了一些基本信息,生產商,IRQ中斷號,還有就是定義了mem空間和io空間的起始地址和大小。



CPU通過兩個寄存器訪問PCI設備的配置空間:CFG_ADDR 和 CFG_DATA




  對CFG_DATA的操作就是對配置空間相應寄存器的操作,沒有專門的bit來制定操作類型r/w。

   PCI配置空間中的BAR(Base Address Register)用來映射PCI設備的寄存器,里面的值是bus地址首地址,至於空間的大小,先向bar中寫0xFFFFFFFFF,然后讀取,選最低的一位非0的,比如為0x1000,那個空間的大小就為0x1000。
   這里需要注意,當PCI配置成64bit或32bit時,BAR有區別
  
    
3.PCI地址轉換

   當CPU訪問PCI設備的mem空間和io空間的寄存器時,需要進行地址轉換。
MPC8560有5個outbound窗口0~5用來將CPU內部地址轉換為PCI總線地址。上電時只有窗口0使能的,其他都是disable狀態。每個outbound窗口有如下三類寄存器:
    1.外部PCI總線地址的基址
    2.CPU內部32bit地址的基址(EA)    
    3.窗口屬性寄存器,大小、轉換類型等等

  outbound窗口轉換后(外部PCI總線地址的基址,當使用64bit PCI時會用到第二個擴展寄存器)  

  
outbound窗口轉換前地址(CPU內部32bit地址的基址)


outbound窗口屬性寄存器



EN:設置此窗口是否使能
RTT/WTT:分別設置此窗口的存取方式(memory或io)
OWS:設置此窗口大小


    同理,當PCI設備訪問MPC8560時,有3個inbound窗口1~3用來將PCI總線地址轉換為CPU內部地址。和outbound窗口一樣,inbound窗口有如下三類寄存器:
    CPU內部32bit地址的基址(EA)    
    外部PCI總線地址的基址
    窗口屬性寄存器,大小、轉換類型等等



EN:設置此窗口是否使能
PF:設置此窗口是否開啟prefetchable特性
TGI:Target Interface,見datasheet P883
RTT/WTT:設置PCI外設訪問CPU時的存取方式(snoop L2cache 等等)
IWA:設置此窗口的大小


3.PCI拓撲結構

PCI拓撲結構,有如下例子



    在上圖的總線結構中,ethernet設備和pci-pci bridge的資源空間必須要是pci bus0的一個子集
    同理,SCSI和VIDEO同類型資源必須要是pci_bus1的子集。

    CPU訪問PCI的過程是這樣的(只有一個根總線和pci-pci bridge過濾窗口功能打開的情況):

    1.cpu向pci發出一個I/O請求。
    首先經過根總線.它會判斷是否在它的資源范圍內.如果在它的范圍,它就會丟向總線所在的每一個設備.包括pci bridge. 如果沒有在根總線的資源范圍,則不會處理這個請求.
    2.如果pci設備判斷該地址屬於它的資源范圍,則處理后發出應答
    3.pci bridge接收到這個請求,它會判斷I/O地址是否在它的資源范圍內.如果在它的范圍,它會把它丟到它的下層子線.
    4.下層總線經過經過相同的處理后,就會找到這個PCI設備了

    一個PCI設備訪問其它PCI設備或者其它外設的過程:

    1.首先這個PCI發出一個請求,這個請求會在總線上廣播
    2.如果要請求的設備是在同級總線,就會產生應答
    3.請求的設備不是在同層總線,就會進行pci bridge.pci橋判斷該請求不在它的范圍內(目的地不是它下層的設備),就會將它丟向上層.
    4.這樣輾轉之后,就能找到對應的設備了


3.PCI枚舉過程


通過PCI枚舉,CPU知道當前系統上有多少PCI設備,多少根PCI總線,PCI配置空間初始化。

    PCI 總線掃描的原理是從總線 0 掃描到總線 255,對於每條總線,系統都會掃描所有(總線號,設備號,功能號),讀出每個設備配置空間的Device ID和Vendor ID寄存器,如果這兩個寄存器的值是個無效值(0xFFFF),則說明當前位置上沒有設備,接着掃描下一個位置。
    如果是有效值(非0xFFFF),當前位置是個有效的 PCI 設備/橋。進而再讀取該設備的 Header Type 寄存器,如果該寄存器為 1,則表示當前設備是 PCI 橋,否則是 PCI 設備。

Register Number:配置空間寄存器偏移量
Function Number:多功能設備有多個功能號
Device Number:設備編號
Bus Number:總線編號




對所有 PCI 總線進行編號
    PCI 橋如何知道它所連接的 PCI 總線情況呢?這就需要對 PCI 橋進行總線編號。前面介紹過 PCI 橋提供了 Primary Bus Number、Secondary Bus Number 和 Subordinate Bus Number 三個寄存器用於標志該橋所連接的 PCI 總線,下面通過一個示例來說明內核對於 PCI 總線是如何進行編號的。



1.系統運行初始,Bus A 為 0,通過上面的 PCI 總線掃描得到連接在 Bus A 上的 PCI 橋(即圖中Bridge 1) 
2.下面開始設置 Bridge 1 的 Bus 寄存器。將 Primary Bus Number 寄存器設置成 Bus A 的編號,即 0。將 Secondary Bus Number 寄存器設置成 Bus B 的編號,它的值等於(Bus A + 1),也就是 1。由於暫時無法知道該橋所能訪問的所有下行總線數目,Subordinate Bus Number 寄存器暫時設置成 0xFF。 
3.當掃描完所有 Bus A 上所有(設備號,功能號)后,開始掃描 Bus B,Bus B 的編號在掃描完 Bus A 后已經得到,為 1。Bus B 的掃描方法同步驟(1),先掃描出 Bus B 上的 PCI 橋(即圖中的 Bridge 2),然后配置 Primary Bus Number 寄存器為 1,Secondary Bus Number 寄存器為 2,而 Subordinate Bus Number 寄存器依然為 0xFF。 
4.Bus B 掃描完后得到 Bus C 的編號,為2。下面開始掃描 Bus C,因為 Bus C 上沒有 PCI 橋,於是在掃描完其它(設備號,功能號)后,Bus C 的掃描結束。 
5.由於 Bridge 2 所能訪問到的最大 Bus 編號是 2,因此重新設置 Bridge 2 的 Subordinate Bus Number 寄存器為 2。 
6.由於 Bridge 1 所能訪問到的最大 Bus 編號也是 2,因此重新設置 Bridge 1 的 Subordinate Bus Number 寄存器為 2。 
7.總線編號結束。 

3.Linux內核PCI數據結構


內核(linux-2.6.24) 提供了三類數據結構用以描述 PCI 控制器、PCI 設備以及 PCI 總線。
數據結構關系如下所示


PCI 控制器 用 pci_controller 結構來描述,它有以下幾個主要的屬性:
index:該屬性標志 PCI 控制器的編號。 
next:該屬性指向下一個 PCI 控制器,通過 next 屬性,PCI 控制器可以形成一個單向鏈表。 
first_busno:該屬性標志了連接在該控制器上第一條總線的編號。 
last_busno:該屬性標志了連接在該控制器上最后一條總線的編號。 
ops:該屬性標志了當前 PCI 控制器所對應的 PCI 配制空間讀寫操作函數。 
io_space:該屬性標志了當前 PCI 控制器所支持的 IO 地址空間。 
mem_space:該屬性標志了當前 PCI 控制器所支持的 Memory 地址區間。 
cfg_addr:該屬性標志了當前 PCI 控制器發起 Configuration 訪問方式時所需要寫入的地址空間。 
cfg_data:該屬性標志了當前 PCI 控制器發起 Configuration 訪問方式時所需要讀寫的數據空間。 
bus:該屬性標志了當前 PCI 控制器所連接的 PCI 總線,它對應的數據結構是 pci_bus。 

PCI 總線 用 pci_bus 結構來描述,它有以下幾個主要的屬性:
parent:可通過該屬性索引到上層 PCI 總線。 
self:該屬性標志了連接的上行 PCI 橋(對應的數據結構是 pci_dev)。 
children:該屬性標志了總線連接的所有 PCI 子總線鏈表。 
devices:該屬性標志了總線連接的所有 PCI 設備鏈表。 
ops:該屬性標志了總線上所有 PCI 設備的配制空間讀寫操作函數。 
number:該屬性標志了當前 PCI 總線的編號。 
primary:該屬性標志了 PCI 上行總線編號。 
secondary:該屬性標志了 PCI 下行總線編號。 
subordinate:該屬性標志了能夠訪問到的最大總線編號。 
resource:該屬性標志了 Memory/IO 地址空間。 

PCI 設備 用 pci_dev 結構來描述,它有以下幾個主要的屬性:
global_list:Linux 定義了一個全局列表來索引所有PCI 設備,該屬性標志了這個全局列表的首指針。 
bus:該屬性標志了當前設備所在的 PCI 總線(對應的數據結構是 pci_bus)。 
devfn:該屬性標志了設備編號和功能編號。 
vendor:該屬性標志了供應商編號。 
device:該屬性標志了設備編號。 
driver:該屬性標志了設備對應的驅動代碼(對應的數據結構是 pci_driver)。 
irq:該屬性標志了中斷號。 
resource:該屬性標志了 Memory/IO 地址區間。 


當 Linux 內核在做 PCI 初始化工作時,它會根據建立一個由 pci_controller、pci_bus 和 pci_dev 三者組成的一個組織結構圖。根據這個結構,軟件開發者可以很方便的通過 PCI 控制器索引到每個 PCI 設備或者 PCI 總線。


4.Linux的PCI子系統初始化流程



第一步:Linux分配數據結構pci_contoller,並初始化,包括PCI的mem/io空間范圍和訪問PCI配置空間所需的handler。
第二步:PCI設備的枚舉:掃描系統上所有PCI設備,初始化它們的配置空間。(硬件上的初始化)
第三步:用數據結構將PCI設備信息聯系起來,構建PCI樹。(軟件上的初始化)
第四步:加載PCI設備驅動。


4.1 初始化PCI控制器

pci_controller結構是內核描述PCI子系統信息的數據結構,里面定義了可供PCI設備使用的mem資源和io資源的范圍,訪問pci設備配置空間的handler等等。
函數調用關系:
start_kernel --> mpc8560ads_setup_arch --> mpc85xx_setup_hose()
此函數分配並初始化了pci_controller

mpc85xx_setup_hose()
    -->pcibios_alloc_controller()                 //分配數據結構pci_controller
    ppc_md.pci_map_irq = mpc85xx_map_irq;    //用來獲得pci設備irq號的handler
    -->setup_indirect_pci() //設置pci_controller里訪問PCI配置空間的鈎子函數
                            //使用ioremap后的CFG_ADDR和CFG_DATA的虛擬地址
    -->mpc85xx_setup_pci1() //設置PCI inbound和outbound窗口寄存器。


PCI子系統的mem和io地址空間范圍也在函數mpc85xx_setup_hose()中定義:
    pci_controller *hose_a;
    hose_a->mem_space.start = MPC85XX_PCI1_LOWER_MEM;
    hose_a->mem_space.end = MPC85XX_PCI1_UPPER_MEM;
    hose_a->io_space.start = MPC85XX_PCI1_LOWER_IO;
    hose_a->io_space.end = MPC85XX_PCI1_UPPER_IO;


4.2 PCI枚舉過程

目前為止,內核只知道PCI子系統總的mem資源和io資源地址范圍。
接下來,內核需要掃描所有PCI設備,把這些資源分配給PCI設備,具體方法參考前面講的PCI枚舉過程。

內核分配mem資源時是從高地址開始分配的。

 mpc85xx_setup_hose()
    -->pciauto_bus_scan()
    for (pci_devfn = 0; pci_devfn < 0xff; pci_devfn++) {    //遍歷0號總線上所有PCI設備
        if (讀當前設備配置空間PCI_HEADER_TYPE失敗)
            continue;                        //當前設備不存在,掃描下一個
        讀當前設備配置空間PCI_CLASS_REVISION
        If(當前設備是PCI橋){
            pciauto_setup_bars()            //分配pci_controller的資源的子集
            pciauto_prescan_setup_bridge()      //寫PCI橋配置空間 PCI_PRIMARY_BUS
                                                //PCI_SECONDARY_BUS
                                                //PCI_SUBORDINATE_BUS
            pciauto_bus_scan()                        //遞歸掃描下一條pci bus 
            pciauto_postscan_setup_bridge()            //寫PCI橋配置空間PCI_SUBORDINATE_BUS
        }
        。。。。。略去
        else {
            //當前設備是PCI設備
            pciauto_setup_bars()
        }
    }

注意:執行完 pciauto_bus_scan后,所有pci設備(包括橋)的配置空間都已經傻瓜式的簡單初始化
      但這些橋和設備還沒有通過數據結構組織起來,這些工作要在第四步 pcibios_init里來完成


4.3 創建PCI樹

上面說了,PCI設備配置空間都初始化差不多了,但是PCI設備還沒有通過數據結構組織起來。    

do_initcalls()
    -->pcibios_init()    //所在文件 arch/ppc/kernel/pci.c

pcibios_init()    
    (1):為PCI設備構造數據結構,組織成PCI樹
    -->pci_scan_bus(hose->first_busno, hose->ops, hose)    //hose就是上面的pci_controller結構
        -->pci_scan_bus_parented
            -->pci_create_bus        // 建立 PCI bus 0 對應的數據結構,這個bus的資源尚未初始化
            -->pci_scan_child_bus    // 從PCI bus 0 開始掃描生成PCI樹,使用了遞歸 
                -->pci_scan_slot
                    -->pci_scan_single_dev
                        -->pci_scan_device()    //創建 pci_dev結構
                            -->pci_setup_device()    //區分橋與設備,分別進行初始化
                                -->pci_read_bases();    //在這才初始化了pci_dev->resource[]

pci_dev->resouce[]中保存的才是cpu internal address(EA),可以對這些地址進行用ioremap,在驅動程序對bar所指位置讀寫的時候一定要用這個

                            
    (2)給PCI設備分配IRQ號
    -->pci_fixup_irqs()
        //遍歷所有pci設備,調用pdev_fixup_irq()
        -->pdev_fixup_irq
            -->ppc_md.pci_swizzle()     //實際調用common_swizzle(),獲得pci所在slot編號
            讀PCI設備配置空間PCI_INTERRUPT_PIN,獲得中斷pin編號[1到4]
            之所以是1到4,因為PCI規范里設備最多4個管腳(除第一個外,其他3個僅用於多功能設備) 
            -->ppc_md.pci_map_irq() //實際調用mpc85xx_map_irq()
                                    //根據slot,pin 和 pci_irq_table[][4]
                                    //來計算出irq號
            -->pcibios_update_irq(pci_dev,irq)
              //將irq號寫入pci設備的配置空間PCI_INTERRUPT_LINE
              //注意,這里寄存器只是用來保存結果,例如把其值8改為9,並不能改變中斷號
    (3)PCI結構樹有了,現在構建PCI的資源樹,有沖突就修改
    -->pcibios_allocate_bus_resource()
        //只考慮pci_bus,形成bus級資源樹(並同時check,資源沖突了就修改)
    -->pcibios_allocate_resources()
        //把pci_dev也考慮進去,完成資源樹


4.3 加載PCI設備驅動

以e1000 PCI 網卡 82546GB為例,講解一個PCI驅動的加載過程
驅動里有如下代碼:
static struct pci_driver e1000_driver = {
    .name     = e1000_driver_name,
    .id_table = e1000_pci_tbl,
    .probe    = e1000_probe,
    .remove   = __devexit_p(e1000_remove),
#ifdef CONFIG_PM
    /* Power Managment Hooks */
    .suspend  = e1000_suspend,
    .resume   = e1000_resume,
#endif
    .shutdown = e1000_shutdown,
    .err_handler = &e1000_err_handler
};

module_init(e1000_init_module); 



do_initcalls()
    -->e1000_init_module
        pci_register_driver(&e1000_driver)
        // e1000_driver.driver.bus = &pci_bus_type;
    -->driver_register(e1000_driver.driver);
    //pci_bus_type.probe() 非空,即調用pci_device_probe()

pci_device_probe(*device)
    -->__pci_device_probe(*pci_driver,*pci_dev)
        -->pci_match_device(*pci_driver,*pci_dev)
        -->pci_call_probe(*pci_driver,*pci_dev,pci_device_id)
            -->drv->probe(*pci_device,*pci_device_id)
            即執行 e1000_probe(*pci_device,*pci_device_id)

接下來就是e1000 PCI 網卡驅動的具體代碼。

 

轉:http://blog.chinaunix.net/uid-24148050-id-101021.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM