FPGA:PCI項目
FPGA 是功能強大的 PCI 開發平
本文引用地址:http://www.ex-cimer.com/article/202401/454595.htmPCI 0 - 簡單的PCI接口
臺,這要歸功于其可重新編程性和運行速度。
// 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 版本包括 PCI 2.3 和 PCI 3.0。
PCI 規范
PCI 由一個名為 PCI 特別興趣小組(簡稱 PCI-SIG)的小組開發和維護。與以太網規范不同,PCI規范不能免費下載。 您需要成為 PCI-SIG 的成員才能訪問該規范。 由于成為會員的費用很高,您可能需要檢查您公司的硬件組(假設您在半導體行業工作),看看您是否可以訪問該規范。
否則,這里有一個簡短的介紹,然后是一些鏈接以獲取更多信息。
PCI特性
PCI總線有4個主要特點:同步
面向事務/突發
總線母帶
即插即用
PCI 是同步的
PCI 總線使用一個時鐘。 默認情況下,時鐘以 33MHz 運行,但可以運行得更低(一直到空閑 = 0MHz)以節省功耗,如果您的硬件支持,也可以運行更高 (66MHz)。PCI 面向事務/突發
PCI是面向事務的。您開始交易
指定起始地址(一個時鐘周期)
您可以根據需要發送任意數量的數據(許多后續時鐘周期)
您結束交易
PCI 允許總線主控
PCI 事務在主從關系中工作。 主服務器是啟動事務(可以是讀取或寫入)的代理。雖然主機 CPU 通常是總線主控器,但所有 PCI 板卡都可能聲明總線并成為總線主站。
PCI是即插即用的
PCI板是即插即用的。這意味著 host-CPU/host-OS 可以:確定PCI總線中每個PCI板卡的標識(制造商和功能(視頻,網絡...))
確定每個板卡的能力/要求(需要多少內存空間,多少個中斷......
重新定位每個主板內存空間
PCI“空間”
PCI 定義了 3 個“空間”,您可以在其中讀取和寫入。當事務開始時,主節點指定事務的起始地址,是讀還是寫,以及他要與哪個空間通信。
內存空間
IO 空間
配置空間
內存和 IO 空間是主力空間。 它們是“可重新定位的”(即每個板響應的地址可以移動)。
配置空間用于即插即用。 在這個空間中,每個板都必須在非常特定的地址實現非常特定的寄存器,以便主機 CPU/OS 可以弄清楚每個板的身份/能力/要求是什么。 從那里,主機 CPU/OS 啟用并配置其他兩個空間。
此空間是固定的,并且始終從所有 PCI 板的地址 0 開始;因此,PCI連接器的一行用作板選擇(僅適用于此空間)。
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 讀寫
IO 事務
最容易使用的 PCI 空間是 IO 空間。沒有來自 CPU/OS 的虛擬化(即 CPU 地址 = 硬件地址)
不需要驅動程序(在 Win98/Me 上為 true,而在 Win XP/2K 上,需要驅動程序,但下面提供了通用驅動程序)
查找可用空間
在 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卡了!
設計注意事項
不使用 PCI_CBE 字節啟用,因此軟件應該只發出 32 位交易,對齊。
您可能會驚訝地發現,PCI“PAR”信號(總線奇偶校驗)也沒有使用。
雖然 PAR 生成是 PCI 合規性所必需的,但它的檢查可能不是因為我可以訪問的 PC 在沒有它的情況下工作正常...... 由于我無法在真實硬件中測試它,所以我省略了它。上面的代碼支持突發傳輸,但當前的 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 塊寄存器可以“按設計”(FPGA 的)保證 0ns 保持時間。
PCI 3 - PCI 邏輯分析儀
現在我們可以在總線上發出讀寫事務,那么“查看”事務的實際情況不是很有趣嗎?
這是用 Dragon 捕獲的一個非常簡單的交易。
在地址階段,CBE 0x3,這意味著“IO 寫入”。
它是地址0x00000000的 IO 寫入,數據0x0200。
FPGA 作為 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卡有三個“空間”嗎?內存空間
IO 空間
配置空間
配置空間是PCI即插即用的核心。 操作系統(Windows、Linux等)首先讀取該信息,以查找是否插入了PCI卡及其特性。
對于簡單的電路板,配置空間僅包含 64 個字節。 它們的重要領域是:
抵消 | 名字 | 功能 | 注意 | 長度 |
---|---|---|---|---|
0 | 供應商 ID | 指定生產商 | ...由 PCI-SIG 分配 | 2 字節 |
2 | 設備 ID | 設備編號 | ...由制造商自己分配 | 2 字節 |
4 | 命令 | 打開和關閉對PCI板的訪問 | ...但配置空間訪問始終處于打開狀態 | 2 字節 |
16 | BAR0(基址寄存器 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 卡應該位于哪個地址。
請參閱 PCI 規范/書籍,了解實際細節。
Windows 即插即用
實現這些寄存器后,操作系統可以發現新硬件。但是操作系統需要驅動程序才能...
。它同意分配內存資源。
PCI 5 - 適用于 Windows 的 PCI 軟件驅動程序
簡單的方法
簡單的方法就是讓別人為你做艱苦的工作!查看 WinDriver。
這是一個商業工具包,可以在幾分鐘內為您構建 PCI 即插即用驅動程序解決方案。
它的工作原理是這樣的:
運行一個向導來檢測您的即插即用設備,包括 PCI 卡。
您選擇您感興趣的卡,為您的設備命名并創建一個“.inf”文件。
這足以讓 Windows 能夠識別硬件并說服他應該使用 WinDriver 的驅動程序。 退出向導,然后通過 Windows 的即插即用硬件檢測來安裝驅動程序。
安裝驅動程序后,再次運行向導,這次是生成一些示例源代碼來訪問 PCI 卡。
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”)
...
評論