新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > FPGA:PCI項目

        FPGA:PCI項目

        作者: 時間:2024-01-09 來源:EEPW編譯 收藏

        是功能強大的 PCI 開發平

        本文引用地址:http://www.104case.com/article/202401/454595.htm

        PCI 0 - 簡單的

        這是 PCI 代碼的一個示例。 我們使用 PCI 寫入命令來控制 LED。 寫“0”可關閉 LED,寫“1”可打開 LED!

        臺,這要歸功于其可重新編程性和運行速度。

        // Very simple PCI target

        // Just 3 flip-flops for the PCI logic, plus one to hold the state of an LED

        module PCI(CLK, RSTn, FRAMEn, AD, CBE, IRDYn, TRDYn, DEVSELn, LED);

        input CLK, RSTn, FRAMEn, IRDYn;

        input [31:0] AD;

        input [3:0] CBE;

        inout TRDYn, DEVSELn;

        output LED;

        parameter IO_address = 32'h00000200;   // we respond to an "IO write" at this address

        parameter CBECD_IOWrite = 4'b0011;

        ////////////////////////////////////////////////////

        reg Transaction;

        wire TransactionStart = ~Transaction & ~FRAMEn;

        wire TransactionEnd = Transaction & FRAMEn & IRDYn;

        wire Targeted = TransactionStart & (AD==IO_address) & (CBE==CBECD_IOWrite);

        wire LastDataTransfer = FRAMEn & ~IRDYn & ~TRDYn;

        always @(posedge CLK or negedge RSTn)

        if(~RSTn) Transaction <= 0;

        else

        case(Transaction)

          1'b0: Transaction <= TransactionStart;

          1'b1: Transaction <= ~TransactionEnd;

        endcase

        reg DevSelOE;

        always @(posedge CLK or negedge RSTn)

        if(~RSTn) DevSelOE <= 0;

        else

        case(Transaction)

          1'b0: DevSelOE <= Targeted;

          1'b1: if(TransactionEnd) DevSelOE <= 1'b0;

        endcase

        reg DevSel;

        always @(posedge CLK or negedge RSTn)

        if(~RSTn) DevSel <= 0;

        else

        case(Transaction)

          1'b0: DevSel <= Targeted;

          1'b1: DevSel <= DevSel & ~LastDataTransfer;

        endcase

        assign DEVSELn = DevSelOE ? ~DevSel : 1'bZ;

        assign TRDYn = DevSelOE ? ~DevSel : 1'bZ;

        wire DataTransfer = DevSel & ~IRDYn & ~TRDYn;

        reg LED; always @(posedge CLK) if(DataTransfer) LED <= AD[0];

        endmodule

        PCI 1 - PCI 的工作原理

        我們在這里專注于 PCI 2.2 32 位,這是當今 PC
        中使用的,較新的 PCI 版本包括 PCI 2.3 和 PCI 3.0。

        PCI 規范

        PCI 由一個名為 PCI 特別興趣小組(簡稱 PCI-SIG)的小組開發和維護。
        與以太網規范不同,PCI規范不能免費下載。 您需要成為 PCI-SIG 的成員才能訪問該規范。 由于成為會員的費用很高,您可能需要檢查您公司的硬件組(假設您在半導體行業工作),看看您是否可以訪問該規范。

        否則,這里有一個簡短的介紹,然后是一些鏈接以獲取更多信息。

        PCI特性

        PCI總線有4個主要特點:
        • 同步

        • 面向事務/突發

        • 總線母帶

        • 即插即用

        PCI 是同步的
        PCI 總線使用一個時鐘。 默認情況下,時鐘以 33MHz 運行,但可以運行得更低(一直到空閑 = 0MHz)以節省功耗,如果您的硬件支持,也可以運行更高 (66MHz)。
        PCI 面向事務/突發
        PCI是面向事務的。
        1. 您開始交易

        2. 指定起始地址(一個時鐘周期)

        3. 您可以根據需要發送任意數量的數據(許多后續時鐘周期)

        4. 您結束交易

        PCI 是 32 位總線,因此有 32 條線來傳輸數據。 在事務開始時,總線用于指定 32 位地址。 一旦指定了地址,就可以經歷許多數據周期。該地址不會重新傳輸,而是在每個數據周期自動遞增。 若要指定其他地址,將停止事務,并啟動新事務。 因此,PCI帶寬在突發模式下得到充分利用。
        PCI 允許總線主控
        PCI 事務在主從關系中工作。 主服務器是啟動事務(可以是讀取或寫入)的代理。
        雖然主機 CPU 通常是總線主控器,但所有 PCI 板卡都可能聲明總線并成為總線主站。

        PCI是即插即用的
        PCI板是即插即用的。這意味著 host-CPU/host-OS 可以:
        • 確定PCI總線中每個PCI板卡的標識(制造商和功能(視頻,網絡...))

        • 確定每個板卡的能力/要求(需要多少內存空間,多少個中斷......

        • 重新定位每個主板內存空間

        最后一個功能是即插即用的重要組成部分。 每塊板都響應一些地址,但可以對它響應的地址進行編程(即每塊板生成自己的板/片選信號)。 這允許操作系統將每個板的地址空間“映射”到他想要的位置。
        PCI“空間”
        PCI 定義了 3 個“空間”,您可以在其中讀取和寫入。
        當事務開始時,主節點指定事務的起始地址,是讀還是寫,以及他要與哪個空間通信。

        1. 內存空間

        2. IO 空間

        3. 配置空間

        它們的工作原理如下:
        • 內存和 IO 空間是主力空間。 它們是“可重新定位的”(即每個板響應的地址可以移動)。

        • 配置空間用于即插即用。 在這個空間中,每個板都必須在非常特定的地址實現非常特定的寄存器,以便主機 CPU/OS 可以弄清楚每個板的身份/能力/要求是什么。 從那里,主機 CPU/OS 啟用并配置其他兩個空間。
          此空間是固定的,并且始終從所有 PCI 板的地址 0 開始;因此,PCI連接器的一行用作板選擇(僅適用于此空間)。

        為了符合要求,PCI板需要實現配置空間。 內存和 IO 空間是可選的,但在實踐中始終使用一個或兩個。

        PCI橋接器

        PCI 設備不直接連接到主機 CPU,而是通過“橋接”芯片。
        這是因為 CPU 通常不會本地“說”PCI,因此橋接器必須將事務從 CPU 總線轉換為 PCI 總線。 此外,CPU 永遠不會像 PCI 設備那樣有 3 個內存空間。 大多數 CPU 有 1 個空間(內存空間),而其他 CPU 有 2 個空間(內存和 IO)。 橋接器必須玩一些技巧,以便 CPU 仍然可以訪問所有 3 個 PCI 空間。

        PCI電壓

        PCI板可以使用3.3V或5V信號。 有趣的是,目前的 PC 都使用 5V 信號。
        PCI 板連接器有一個或兩個插槽,用于識別板是符合 3.3V 還是 5V 標準。 例如,這是為了確保僅 3.3V 的電路板無法插入 PC 的僅 5V PCI 總線。

        下面以純 5V 板為例: 雖然該板同時兼容 5V 和 3.3V:



        PCI 時序

        PCI 指定與其時鐘相關的時序。
        使用33MHz時鐘,我們有:
        • 輸入端7ns/0ns Tsu/Th(建立/保持)約束

        • 輸出端 11ns Tco(時鐘至輸出)

        PCI 2 - PCI 讀寫

        現在讓我們做一些真正的PCI交易......

        IO 事務

        最容易使用的 PCI 空間是 IO 空間。
        • 沒有來自 CPU/OS 的虛擬化(即 CPU 地址 = 硬件地址)

        • 不需要驅動程序(在 Win98/Me 上為 true,而在 Win XP/2K 上,需要驅動程序,但下面提供了通用驅動程序)

        IO 空間的缺點是它很小(在 PC 上限制為 64KB,即使 PCI 支持 4GB)并且非常擁擠。

        查找可用空間

        在 Windows 98/Me 上,打開“設備管理器”(從“控制面板”/系統),然后顯示計算機/屬性并檢查“輸入/輸出 (I/O)”面板。

        在 Windows XP/2000 上,打開“系統信息”程序(程序/附件/系統工具/系統信息),然后單擊“I/O”。

        許多外圍設備都在使用 IO 空間,因此自由空間候選人需要進行一些研究。

        驅動程序

        在 Win98/Me 上,IO 空間不受保護,因此不需要驅動程序。
        對于 WinXP/2K,GiveIO 和 UserPort 是開放 IO 空間的免費通用驅動程序。

        RAM PCI卡

        讓我們在PCI卡中實現一個小的RAM。

        RAM 為 32 位 x 16 個位置。 它足夠小,可以使用“直接尋址”(IO 空間非常擁擠,否則需要間接尋址)。
        我們需要在主機 PC 中選擇一個空閑的 IO 空間。 每個 32 位位置需要 4 個字節地址,因此我們需要 4x16=64 個連續的可用地址。 我們在這里選擇了 0x200-0x23F,但您可能需要選擇其他東西。

        首先是模塊聲明。

        module PCI_RAM( PCI_CLK, PCI_RSTn, PCI_FRAMEn, PCI_AD, PCI_CBE, PCI_IRDYn, PCI_TRDYn, PCI_DEVSELn );
        input PCI_CLK, PCI_RSTn, PCI_FRAMEn, PCI_IRDYn;
        inout [31:0] PCI_AD;
        input [3:0] PCI_CBE;
        output PCI_TRDYn, PCI_DEVSELn;

        parameter IO_address = 32'h00000200;   // 0x0200 to 0x23F
        parameter PCI_CBECD_IORead = 4'b0010;
        parameter PCI_CBECD_IOWrite = 4'b0011;

        然后,我們通過“PCI_Transaction”寄存器跟蹤公交車上發生的事情。
        “PCI_Transaction”在進行任何交易時被斷言,無論是對我們,還是對公共汽車上的任何其他卡。

        reg PCI_Transaction;

        wire PCI_TransactionStart = ~PCI_Transaction & ~PCI_FRAMEn;
        wire PCI_TransactionEnd = PCI_Transaction & PCI_FRAMEn & PCI_IRDYn;

        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_Transaction <= 0;
        else
        case(PCI_Transaction)
          1'b0: PCI_Transaction <= PCI_TransactionStart;
          1'b1: PCI_Transaction <= ~PCI_TransactionEnd;
        endcase

        // We respond only to IO reads/writes, 32-bits aligned
        wire PCI_Targeted = PCI_TransactionStart & (PCI_AD[31:6]==(IO_address>>6)) & (PCI_AD[1:0]==0) & ((PCI_CBE==PCI_CBECD_IORead) | (PCI_CBE==PCI_CBECD_IOWrite));

        // When a transaction starts, the address is available for us to register
        // We just need a 4 bits address here
        reg [3:0] PCI_TransactionAddr;
        always @(posedge PCI_CLK) if(PCI_TransactionStart) PCI_TransactionAddr <= PCI_AD[5:2];

        現在,再增加幾個寄存器,以便能夠聲明交易并記住它是讀取還是寫入

        wire PCI_LastDataTransfer = PCI_FRAMEn & ~PCI_IRDYn & ~PCI_TRDYn;

        // Is it a read or a write?
        reg PCI_Transaction_Read_nWrite;
        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_Transaction_Read_nWrite <= 0;
        else
        if(~PCI_Transaction & PCI_Targeted) PCI_Transaction_Read_nWrite <= ~PCI_CBE[0];

        // Should we claim the transaction?
        reg PCI_DevSelOE;
        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_DevSelOE <= 0;
        else
        case(PCI_Transaction)
          1'b0: PCI_DevSelOE <= PCI_Targeted;
          1'b1: if(PCI_TransactionEnd) PCI_DevSelOE <= 1'b0;
        endcase

        讓我們認領交易。

        // PCI_DEVSELn should be asserted up to the last data transfer
        reg PCI_DevSel;
        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_DevSel <= 0;
        else
        case(PCI_Transaction)
          1'b0: PCI_DevSel <= PCI_Targeted;
          1'b1: PCI_DevSel <= PCI_DevSel & ~PCI_LastDataTransfer;
        endcase

        最后,RAM本身被寫入或讀取,PCI_AD總線相應地驅動。

        // PCI_TRDYn is asserted during the whole PCI_Transaction because we don't need wait-states
        // For read transaction, delay by one clock to allow for the turnaround-cycle
        reg PCI_TargetReady;
        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_TargetReady <= 0;
        else
        case(PCI_Transaction)
          1'b0: PCI_TargetReady <= PCI_Targeted & PCI_CBE[0]; // active now on write, next cycle on reads
          1'b1: PCI_TargetReady <= PCI_DevSel & ~PCI_LastDataTransfer;
        endcase

        // Claim the PCI_Transaction
        assign PCI_DEVSELn = PCI_DevSelOE ? ~PCI_DevSel : 1'bZ;
        assign PCI_TRDYn = PCI_DevSelOE ? ~PCI_TargetReady : 1'bZ;


        wire PCI_DataTransferWrite = PCI_DevSel & ~PCI_Transaction_Read_nWrite & ~PCI_IRDYn & ~PCI_TRDYn;

        // Instantiate the RAM
        // We use Xilinx's synthesis here (XST), which supports automatic RAM recognition
        // The following code creates a distributed RAM, but a blockram could also be used (we have an extra clock cycle to get the data out)
        reg [31:0] RAM [15:0];
        always @(posedge PCI_CLK) if(PCI_DataTransferWrite) RAM[PCI_TransactionAddr] <= PCI_AD;

        // Drive the AD bus on reads only, and allow for the turnaround cycle
        reg PCI_AD_OE;
        always @(posedge PCI_CLK or negedge PCI_RSTn)
        if(~PCI_RSTn) PCI_AD_OE <= 0;
        else
          PCI_AD_OE <= PCI_DevSel & PCI_Transaction_Read_nWrite & ~PCI_LastDataTransfer;

        // Now we can drive the PCI_AD bus
        assign PCI_AD = PCI_AD_OE ? RAM[PCI_TransactionAddr] : 32'hZZZZZZZZ;

        endmodule

        現在我們可以讀寫PCI卡了!

        設計注意事項
        1. 不使用 PCI_CBE 字節啟用,因此軟件應該只發出 32 位交易,對齊。

        2. 您可能會驚訝地發現,PCI“PAR”信號(總線奇偶校驗)也沒有使用。
          雖然 PAR 生成是 PCI 合規性所必需的,但它的檢查可能不是因為我可以訪問的 PC 在沒有它的情況下工作正常...... 由于我無法在真實硬件中測試它,所以我省略了它。

        3. 上面的代碼支持突發傳輸,但當前的 PC 網橋似乎不會發出突發(至少對于 IO 空間)。 x86 處理器支持突發 IO 指令 (REP IN/OUTS),但它們最終被分解為 PCI 總線上的單個事務。 此外,我不確定突發 IO 是否需要自動遞增 IO 地址,特別是因為 REP INS/OUTS 指令不需要。
          但是,由于不遞增對時間有很好的影響(更多細節見下文),因此我以這種方式保留了代碼。

        發出 IO 讀/寫事務

        在 PC 上,使用 x8086“IN”和“OUT”處理器指令發出 IO 事務。
        某些編譯器沒有對這些函數的本機支持,因此您可能必須使用內聯匯編程序函數。下面是 Visual C++ 的示例:

        void WriteIO_DWORD(WORD addr, DWORD data)
        {
          __asm
          {
            mov dx, addr
            mov eax, data
            out dx, eax
          }
        }

        DWORD ReadIO_DWORD(WORD addr)
        {
          __asm
          {
            mov dx, addr
            in eax, dx

          }
        }

        GUI PCI IO 訓練器軟件

        您可以使用這個簡單的 IOtest 應用程序在 PC 上發出 32 位 IO 讀取和寫入。
        這直接適用于 Win98/Me。 確保 GiveIO 或 UserPort 在 WinXP/2K 上運行。 有一點很重要:可用空間在讀取時返回0xFFFFFFFF。

        時序注意事項

        請記住,PCI 需要:

        • 輸入端7ns/0ns Tsu/Th(建立/保持)約束

        • 輸出端 11ns Tco(時鐘至輸出)

        大多數PCI內核都非常復雜,如果不在IO塊中注冊輸入,就不可能滿足Tsu的要求。 如果不對輸出做同樣的事情,也很難滿足 TCO。
        但這些寄存器會增加設計延遲。 上面的代碼非常簡單,不需要 IO 塊寄存器。

        該代碼使用 Dragon 開發板和 Xilinx 的 ISE 軟件進行了測試。
        它給出了類似的東西:

        時序摘要:
        ---------------

        時序誤差:0 成績:0

        設計統計: 最小周期:9.667ns(最大頻率:103.445MHz)
        時鐘前最短輸入所需時間:5.556ns 時鐘后最短輸出所需時間:
        10.932ns


        基本滿足了時鐘頻率(103MHz對33MHz)。
        Tsu 在 PCI_DEVSELn 和 PCI_TRDYn 信號上以很大的優勢(5.556ns 對 7ns)滿足,而 Tco 幾乎沒有滿足(10.932ns 對 11ns)。
        如果必須在突發讀取時自動遞增 IO 地址,則 AD 總線上不會滿足 Tco。 由于地址是靜態的,并且(僅用于讀取周期)PCI總線在地址階段之后需要一個周轉周期,因此數據有一個額外的時鐘周期來準備。 如果沒有它,TCO約為13ns,因此高于最大11ns。 但是有了額外的時鐘周期,我們實際上以 28ns 的松弛(=余量)來滿足時序,這非常舒適。

        唯一未滿足的時序是輸入保持時間(0nS),希望它足夠低(對于最嚴重的違規者為0.3nS)。 但 Xilinx 不支持限制保持時間的方法,可能是因為使用 IO 塊寄存器可以“按設計”( 的)保證 0ns 保持時間。


        PCI 3 - PCI 邏輯分析儀

        現在我們可以在總線上發出讀寫事務,那么“查看”事務的實際情況不是很有趣嗎?

        這是用 Dragon 捕獲的一個非常簡單的交易。

        在地址階段,CBE 0x3,這意味著“IO 寫入”。
        它是地址0x00000000的 IO 寫入,數據0x0200。

        作為 PCI 邏輯分析儀

        能夠看到總線運行可能很有趣:
        • 更好地了解其操作。

        • 檢查事務內和事務之間的總線延遲。

        • 進行事后分析(如果您的 PCI 內核存在功能問題)。

        查看信號通常需要昂貴的設備,如總線擴展器和邏輯分析儀。 這可能很棘手,因為PCI規范不允許每個PCI信號(當然每個PCI卡)上有一個以上的IO負載。 這是因為總線對容性負載或線短截線很敏感,這些負載或短截線會使高速信號失真。

        但是,FPGA不能像邏輯分析儀一樣工作嗎?

        FPGA已經連接到總線,并具有內部存儲器,可用于實時捕獲總線操作。 Dragon 還有一個 USB 接口,可用于轉儲 PCI 捕獲,而不會干擾 PCI 接口實現,即使 PCI 總線“死機”。

        FPGA 還可以輕松創建復雜的觸發條件,這些條件將比大多數邏輯分析儀更智能......如果要在地址 17x0 進行第二次讀取后捕獲第 1234 次寫入,該怎么辦?

        捕獲 PCI 信號

        我們在這里構建了一個“狀態”(=同步)邏輯分析器。

        捕獲的信號是:

        wire [47:0] dsbr = {
          PCI_AD,
          PCI_CBE, PCI_IRDYn, PCI_TRDYn, PCI_FRAMEn, PCI_DEVSELn,
          PCI_IDSEL, PCI_PAR, PCI_GNTn, PCI_LOCKn, PCI_PERRn, PCI_REQn, PCI_SERRn, PCI_STOPn};


        只有 48 個信號!
        很好,如果我們選擇 3 個時鐘的深度,則非常適合 256 個塊。

        實現起來很簡單:一旦設置了觸發條件,一個 8 位計數器開始為模塊提供信號,另一個計數器允許 USB 讀取模塊數據。 還添加了邏輯,以允許一定程度的預觸發采集 - Dragon 板文件中的詳細信息。

        blockram 輸出按此順序多路復用至 USB 控制器

        case(USB_readaddr[2:0])
          3'h0: USB_Data <= bro[ 7: 0];
          3'h1: USB_Data <= bro[15: 8];
          3'h2: USB_Data <= bro[23:16];
          3'h3: USB_Data <= bro[31:24];
          3'h4: USB_Data <= bro[39:32];
          3'h5: USB_Data <= bro[47:40];
          3'h6: USB_Data <= 8'h01;  // padding, added for ease of implementation
          3'h7: USB_Data <= 8'h02;  // padding, added for ease of implementation
        endcase


        最后,使用 USB 批量讀取命令,采集數據并將其保存到“.pciacq”文件中以供進一步分析。
        PCI總線查看器
        用于查看“.pciacq”文件的軟件可以在這里下載。

        包括一個示例“.pciacq”文件,該文件是此事務列表的結果捕獲:

        ReadIO_DWORD( 0x200 );
        ReadIO_DWORD( 0x204 );
        ReadIO_DWORD( 0x208 );
        ReadIO_DWORD( 0x210 );
        WriteIO_DWORD( 0x204, 0x12345678 );
        WriteIO_DWORD( 0x208, 0x87654321 );
        WriteIO_DWORD( 0x210, 0xDEADBEEF );
        ReadIO_DWORD( 0x200 );
        ReadIO_DWORD( 0x204 );
        ReadIO_DWORD( 0x208 );
        ReadIO_DWORD( 0x210 );


        該軟件如下所示: 一件有趣的事情:


        在讀取周轉周期中,AD 總線顯示上一次讀取的數據......

        PCI 4 - PCI 即插即用

        既然讀寫訪問正在進行中,那么PCI即插即用需要什么才能工作?



        我們的PCI卡還沒有在列表中...

        配置空間

        還記得PCI卡有三個“空間”嗎?
        1. 內存空間

        2. IO 空間

        3. 配置空間

        配置空間是PCI即插即用的核心。 操作系統(Windows、Linux等)首先讀取該信息,以查找是否插入了PCI卡及其特性。

        對于簡單的電路板,配置空間僅包含 64 個字節。 它們的重要領域是:

        抵消名字功能注意長度
        0供應商 ID指定生產商...由 PCI-SIG 分配2 字節
        2設備 ID設備編號...由制造商自己分配2 字節
        4命令打開和關閉對PCI板的訪問...但配置空間訪問始終處于打開狀態2 字節
        16BAR0(基址寄存器 0)PCI板應響應的地址...后跟 BAR1 到 BAR5每個 4 個字節

        通過在這些位置實現正確的值和寄存器,操作系統可以“找到”PCI卡。

        配置空間事務

        每個PCI插槽都作為稱為IDSEL的信號。 IDSEL 信號不沿總線共享;每個PCI插槽都有自己的插槽。
        當 PCI 卡在總線上看到配置空間事務,并且斷言其自己的 IDSEL 時,它知道它應該響應。


        parameter PCI_CBECD_CSRead = 4'b1010;   // configuration space read
        parameter PCI_CBECD_CSWrite = 4'b1011;   // configuration space write

        wire PCI_Targeted = PCI_TransactionStart & PCI_IDSEL & ((PCI_CBE==PCI_CBECD_CSRead) | (PCI_CBE==PCI_CBECD_CSWrite)) & (PCI_AD[1:0]==0);

        之后,它可以是讀取或寫入,但它的工作方式與內存或 IO 空間相同。

        一些細節:
        • 對于供應商 ID,我們只需選擇一個數字;我們只是在實驗,對吧?好的,0x0100工作正常。

        • 設備 ID 可以保留為 0

        • 命令位 0 是 IO 空間的“開/關”位,而位 1 是內存空間的“開/關”位。

        • BAR0 是操作系統寫入的寄存器,一旦它決定 PCI 卡應該位于哪個地址。

        還有一些其他細節被遺漏了,比如 BAR0 的某些部分是只讀的......
        請參閱 PCI 規范/書籍,了解實際細節。

        Windows 即插即用

        實現這些寄存器后,操作系統可以發現新硬件。




        但是操作系統需要驅動程序才能...




        。它同意分配內存資源。


        PCI 5 - 適用于 Windows 的 PCI 軟件驅動程序

        現在我們需要PCI卡的驅動程序,有兩種方法可以獲取它。

        簡單的方法

        簡單的方法就是讓別人為你做艱苦的工作!

        查看 WinDriver。
        這是一個商業工具包,可以在幾分鐘內為您構建 PCI 即插即用驅動程序解決方案。

        它的工作原理是這樣的:

        • 運行一個向導來檢測您的即插即用設備,包括 PCI 卡。

        • 您選擇您感興趣的卡,為您的設備命名并創建一個“.inf”文件。

        • 這足以讓 Windows 能夠識別硬件并說服他應該使用 WinDriver 的驅動程序。 退出向導,然后通過 Windows 的即插即用硬件檢測來安裝驅動程序。

        • 安裝驅動程序后,再次運行向導,這次是生成一些示例源代碼來訪問 PCI 卡。

        WinDriver 給您 30 天的試用時間。
        Windriver 可能不錯,但 2000 美元,如果您只想嘗試 PCI 即插即用機制,那就太貴了。

        艱難的道路

        使用 Microsoft Windows DDK。

        安裝 Windows DDK
        最新的 Windows DDK 版本不是免費的,而早期的化身 (98/2000) 可以免費下載。
        DDK 易于安裝。 對于 Win98 和 Win2000 DDK,首先安裝 Visual C++ 5.0 或 6.0,然后安裝 DDK 本身。 然后按照“install.htm”說明使用“build”命令生成一些示例驅動程序。
        最低 WDM 即插即用驅動程序
        以下是 Windows 設備管理器分配 PCI 卡使用的內存資源所需的最少代碼。
        由于它是一個WDM驅動程序,所以它可以在WinXP/2000/98中工作。

        WDM 驅動程序的入口點是“DriverEntry”函數(類似于 C 程序的“main”)。
        其主要目的是發布回調函數的地址。 我們的最低驅動程序只需要 2 個。

        NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
        {
          DriverObject->DriverExtension->AddDevice = DevicePCI_AddDevice;
          DriverObject->MajorFunction[IRP_MJ_PNP] = DevicePCI_PnP;

          return STATUS_SUCCESS;

        }

        WDM 驅動程序至少創建一個“設備”(如果你的電腦有多個類似的項目,則同一 WDM 驅動程序可能會創建多個設備)。 在驅動程序可以創建設備之前,我們需要一個“設備擴展”結構。 每個設備都使用該結構來存儲信息。 我們可以讓它變得盡可能大,一個典型的設備會在其中存儲許多字段。 我們的最小設備只需要一個字段。

        typedef struct
        {
          PDEVICE_OBJECT NextStackDevice;
        }
        DevicePCI_DEVICE_EXTENSION, *PDevicePCI_DEVICE_EXTENSION;


        這個“NextStackDevice”是干什么用的?WDM實現細節...
        WDM 設備處理 IRP(“I/O 請求數據包”、創建/讀取/寫入/關閉...... WDM 設備不是單獨工作的,而是組裝在設備的邏輯“堆棧”中。 IRP 請求沿堆棧發送,并在途中進行處理。 堆棧是從下到上創建的(底部=硬件層,頂部=邏輯層)。 創建堆棧時,每個設備都會將自身附加到正下方的設備。 設備通常將有關設備的信息存儲在設備擴展的正下方,以便以后可以轉發 IRP 請求。 設備并不真正知道它在堆棧中的位置,它只是在請求到來時處理或轉發請求。

        無論如何,現在我們可以實現DevicePCI_AddDevice
        它創建一個設備對象,并將設備附加到設備堆棧。

        NTSTATUS DevicePCI_AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
        {
          // Create the device and allocate the "Device Extension"
          PDEVICE_OBJECT fdo;
          NTSTATUS status = IoCreateDevice(DriverObject, sizeof(DevicePCI_DEVICE_EXTENSION), NULL, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo);
          if(!NT_SUCCESS(status)) return status;

          // Attach to the driver below us
          PDevicePCI_DEVICE_EXTENSION dx = (PDevicePCI_DEVICE_EXTENSION)fdo->DeviceExtension;
          dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, pdo);

          fdo->Flags &= ~DO_DEVICE_INITIALIZING;
          return STATUS_SUCCESS;

        }

        最后,我們可以處理即插即用 IRP 請求。
        我們的最小設備僅處理START_DEVICE和REMOVE_DEVICE請求。

        NTSTATUS DevicePCI_PnP(PDEVICE_OBJECT fdo, PIRP IRP)
        {
          PDevicePCI_DEVICE_EXTENSION dx = (PDevicePCI_DEVICE_EXTENSION)fdo->DeviceExtension;
          PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(IRP);
          ULONG MinorFunction = IrpStack->MinorFunction;

          switch(MinorFunction)
          {
          case IRP_MN_START_DEVICE:
            // we should check the allocated resource...
            break;
          case IRP_MN_REMOVE_DEVICE:
            status = IRP_NotCompleted(fdo, IRP);
            if(dx->NextStackDevice) IoDetachDevice(dx->NextStackDevice);
            IoDeleteDevice(fdo);
            break;
          }

          // call the device below us
          IoSkipCurrentIrpStackLocation(IRP);
          return IoCallDriver(dx->NextStackDevice, IRP);

        }

        START_DEVICE請求是我們接受或拒絕內存資源的請求。 在這里,我們什么都不做,只是將請求向下轉發到堆棧中,在那里它總是被接受。



        現在,我們的設備獲得了一些內存資源,但對它們不做任何事情。
        為了更有用,驅動程序需要:
        • 在接受內存資源之前檢查它們

        • 導出設備名稱

        • 實現一些“DeviceIOcontrol”以與 Win32 應用程序通信

        • 處理更多 IO 請求 (“IRP”)

        • ...



        關鍵詞: FPGA PCI接口

        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 巫溪县| 澳门| 灵山县| 乌兰浩特市| 城口县| 赤峰市| 龙泉市| 湖南省| 双流县| 康马县| 盐山县| 罗山县| 汤阴县| 登封市| 晴隆县| 醴陵市| 仁怀市| 天全县| 米易县| 四会市| 江山市| 漠河县| 陇西县| 衡阳市| 溧水县| 林芝县| 恩施市| 宁海县| 宝坻区| 桃江县| 宜丰县| 紫云| 永丰县| 健康| 锡林郭勒盟| 太和县| 秭归县| 来凤县| 广南县| 昆明市| 城固县|