FPGA:PCI項(xiàng)目
FPGA 是功能強(qiáng)大的 PCI 開發(fā)平
本文引用地址:http://www.bjwjmy.cn/article/202401/454595.htmPCI 0 - 簡單的PCI接口
臺,這要?dú)w功于其可重新編程性和運(yùn)行速度。
// 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 規(guī)范
PCI 由一個(gè)名為 PCI 特別興趣小組(簡稱 PCI-SIG)的小組開發(fā)和維護(hù)。與以太網(wǎng)規(guī)范不同,PCI規(guī)范不能免費(fèi)下載。 您需要成為 PCI-SIG 的成員才能訪問該規(guī)范。 由于成為會員的費(fèi)用很高,您可能需要檢查您公司的硬件組(假設(shè)您在半導(dǎo)體行業(yè)工作),看看您是否可以訪問該規(guī)范。
否則,這里有一個(gè)簡短的介紹,然后是一些鏈接以獲取更多信息。
PCI特性
PCI總線有4個(gè)主要特點(diǎn):同步
面向事務(wù)/突發(fā)
總線母帶
即插即用
PCI 是同步的
PCI 總線使用一個(gè)時(shí)鐘。 默認(rèn)情況下,時(shí)鐘以 33MHz 運(yùn)行,但可以運(yùn)行得更低(一直到空閑 = 0MHz)以節(jié)省功耗,如果您的硬件支持,也可以運(yùn)行更高 (66MHz)。PCI 面向事務(wù)/突發(fā)
PCI是面向事務(wù)的。您開始交易
指定起始地址(一個(gè)時(shí)鐘周期)
您可以根據(jù)需要發(fā)送任意數(shù)量的數(shù)據(jù)(許多后續(xù)時(shí)鐘周期)
您結(jié)束交易
PCI 允許總線主控
PCI 事務(wù)在主從關(guān)系中工作。 主服務(wù)器是啟動事務(wù)(可以是讀取或?qū)懭耄┑拇怼?br/>雖然主機(jī) CPU 通常是總線主控器,但所有 PCI 板卡都可能聲明總線并成為總線主站。PCI是即插即用的
PCI板是即插即用的。這意味著 host-CPU/host-OS 可以:確定PCI總線中每個(gè)PCI板卡的標(biāo)識(制造商和功能(視頻,網(wǎng)絡(luò)...))
確定每個(gè)板卡的能力/要求(需要多少內(nèi)存空間,多少個(gè)中斷......
重新定位每個(gè)主板內(nèi)存空間
PCI“空間”
PCI 定義了 3 個(gè)“空間”,您可以在其中讀取和寫入。當(dāng)事務(wù)開始時(shí),主節(jié)點(diǎn)指定事務(wù)的起始地址,是讀還是寫,以及他要與哪個(gè)空間通信。
內(nèi)存空間
IO 空間
配置空間
內(nèi)存和 IO 空間是主力空間。 它們是“可重新定位的”(即每個(gè)板響應(yīng)的地址可以移動)。
配置空間用于即插即用。 在這個(gè)空間中,每個(gè)板都必須在非常特定的地址實(shí)現(xiàn)非常特定的寄存器,以便主機(jī) CPU/OS 可以弄清楚每個(gè)板的身份/能力/要求是什么。 從那里,主機(jī) CPU/OS 啟用并配置其他兩個(gè)空間。
此空間是固定的,并且始終從所有 PCI 板的地址 0 開始;因此,PCI連接器的一行用作板選擇(僅適用于此空間)。
PCI橋接器
PCI 設(shè)備不直接連接到主機(jī) CPU,而是通過“橋接”芯片。這是因?yàn)?CPU 通常不會本地“說”PCI,因此橋接器必須將事務(wù)從 CPU 總線轉(zhuǎn)換為 PCI 總線。 此外,CPU 永遠(yuǎn)不會像 PCI 設(shè)備那樣有 3 個(gè)內(nèi)存空間。 大多數(shù) CPU 有 1 個(gè)空間(內(nèi)存空間),而其他 CPU 有 2 個(gè)空間(內(nèi)存和 IO)。 橋接器必須玩一些技巧,以便 CPU 仍然可以訪問所有 3 個(gè) PCI 空間。
PCI電壓
PCI板可以使用3.3V或5V信號。 有趣的是,目前的 PC 都使用 5V 信號。PCI 板連接器有一個(gè)或兩個(gè)插槽,用于識別板是符合 3.3V 還是 5V 標(biāo)準(zhǔn)。 例如,這是為了確保僅 3.3V 的電路板無法插入 PC 的僅 5V PCI 總線。
下面以純 5V 板為例: 雖然該板同時(shí)兼容 5V 和 3.3V:
PCI 時(shí)序
PCI 指定與其時(shí)鐘相關(guān)的時(shí)序。使用33MHz時(shí)鐘,我們有:
輸入端7ns/0ns Tsu/Th(建立/保持)約束
輸出端 11ns Tco(時(shí)鐘至輸出)
PCI 2 - PCI 讀寫
IO 事務(wù)
最容易使用的 PCI 空間是 IO 空間。沒有來自 CPU/OS 的虛擬化(即 CPU 地址 = 硬件地址)
不需要驅(qū)動程序(在 Win98/Me 上為 true,而在 Win XP/2K 上,需要驅(qū)動程序,但下面提供了通用驅(qū)動程序)
查找可用空間
在 Windows 98/Me 上,打開“設(shè)備管理器”(從“控制面板”/系統(tǒng)),然后顯示計(jì)算機(jī)/屬性并檢查“輸入/輸出 (I/O)”面板。
在 Windows XP/2000 上,打開“系統(tǒng)信息”程序(程序/附件/系統(tǒng)工具/系統(tǒng)信息),然后單擊“I/O”。
驅(qū)動程序
在 Win98/Me 上,IO 空間不受保護(hù),因此不需要驅(qū)動程序。對于 WinXP/2K,GiveIO 和 UserPort 是開放 IO 空間的免費(fèi)通用驅(qū)動程序。
RAM PCI卡
讓我們在PCI卡中實(shí)現(xiàn)一個(gè)小的RAM。RAM 為 32 位 x 16 個(gè)位置。 它足夠小,可以使用“直接尋址”(IO 空間非常擁擠,否則需要間接尋址)。
我們需要在主機(jī) PC 中選擇一個(gè)空閑的 IO 空間。 每個(gè) 32 位位置需要 4 個(gè)字節(jié)地址,因此我們需要 4x16=64 個(gè)連續(xù)的可用地址。 我們在這里選擇了 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”寄存器跟蹤公交車上發(fā)生的事情。
“PCI_Transaction”在進(jìn)行任何交易時(shí)被斷言,無論是對我們,還是對公共汽車上的任何其他卡。
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];
現(xiàn)在,再增加幾個(gè)寄存器,以便能夠聲明交易并記住它是讀取還是寫入
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
讓我們認(rèn)領(lǐng)交易。
// 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總線相應(yīng)地驅(qū)動。
// 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
現(xiàn)在我們可以讀寫PCI卡了!
設(shè)計(jì)注意事項(xiàng)
不使用 PCI_CBE 字節(jié)啟用,因此軟件應(yīng)該只發(fā)出 32 位交易,對齊。
您可能會驚訝地發(fā)現(xiàn),PCI“PAR”信號(總線奇偶校驗(yàn))也沒有使用。
雖然 PAR 生成是 PCI 合規(guī)性所必需的,但它的檢查可能不是因?yàn)槲铱梢栽L問的 PC 在沒有它的情況下工作正常...... 由于我無法在真實(shí)硬件中測試它,所以我省略了它。上面的代碼支持突發(fā)傳輸,但當(dāng)前的 PC 網(wǎng)橋似乎不會發(fā)出突發(fā)(至少對于 IO 空間)。 x86 處理器支持突發(fā) IO 指令 (REP IN/OUTS),但它們最終被分解為 PCI 總線上的單個(gè)事務(wù)。 此外,我不確定突發(fā) IO 是否需要自動遞增 IO 地址,特別是因?yàn)?REP INS/OUTS 指令不需要。
但是,由于不遞增對時(shí)間有很好的影響(更多細(xì)節(jié)見下文),因此我以這種方式保留了代碼。
發(fā)出 IO 讀/寫事務(wù)
在 PC 上,使用 x8086“IN”和“OUT”處理器指令發(fā)出 IO 事務(wù)。
某些編譯器沒有對這些函數(shù)的本機(jī)支持,因此您可能必須使用內(nèi)聯(lián)匯編程序函數(shù)。下面是 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 訓(xùn)練器軟件
您可以使用這個(gè)簡單的 IOtest 應(yīng)用程序在 PC 上發(fā)出 32 位 IO 讀取和寫入。
這直接適用于 Win98/Me。 確保 GiveIO 或 UserPort 在 WinXP/2K 上運(yùn)行。 有一點(diǎn)很重要:可用空間在讀取時(shí)返回0xFFFFFFFF。
時(shí)序注意事項(xiàng)
請記住,PCI 需要:
輸入端7ns/0ns Tsu/Th(建立/保持)約束
輸出端 11ns Tco(時(shí)鐘至輸出)
大多數(shù)PCI內(nèi)核都非常復(fù)雜,如果不在IO塊中注冊輸入,就不可能滿足Tsu的要求。 如果不對輸出做同樣的事情,也很難滿足 TCO。
但這些寄存器會增加設(shè)計(jì)延遲。 上面的代碼非常簡單,不需要 IO 塊寄存器。
該代碼使用 Dragon 開發(fā)板和 Xilinx 的 ISE 軟件進(jìn)行了測試。
它給出了類似的東西:
時(shí)序摘要: --------------- 時(shí)序誤差:0 成績:0 設(shè)計(jì)統(tǒng)計(jì): 最小周期:9.667ns(最大頻率:103.445MHz) 時(shí)鐘前最短輸入所需時(shí)間:5.556ns 時(shí)鐘后最短輸出所需時(shí)間: 10.932ns |
基本滿足了時(shí)鐘頻率(103MHz對33MHz)。
Tsu 在 PCI_DEVSELn 和 PCI_TRDYn 信號上以很大的優(yōu)勢(5.556ns 對 7ns)滿足,而 Tco 幾乎沒有滿足(10.932ns 對 11ns)。
如果必須在突發(fā)讀取時(shí)自動遞增 IO 地址,則 AD 總線上不會滿足 Tco。 由于地址是靜態(tài)的,并且(僅用于讀取周期)PCI總線在地址階段之后需要一個(gè)周轉(zhuǎn)周期,因此數(shù)據(jù)有一個(gè)額外的時(shí)鐘周期來準(zhǔn)備。 如果沒有它,TCO約為13ns,因此高于最大11ns。 但是有了額外的時(shí)鐘周期,我們實(shí)際上以 28ns 的松弛(=余量)來滿足時(shí)序,這非常舒適。
唯一未滿足的時(shí)序是輸入保持時(shí)間(0nS),希望它足夠低(對于最嚴(yán)重的違規(guī)者為0.3nS)。 但 Xilinx 不支持限制保持時(shí)間的方法,可能是因?yàn)槭褂?IO 塊寄存器可以“按設(shè)計(jì)”(FPGA 的)保證 0ns 保持時(shí)間。
PCI 3 - PCI 邏輯分析儀
現(xiàn)在我們可以在總線上發(fā)出讀寫事務(wù),那么“查看”事務(wù)的實(shí)際情況不是很有趣嗎?
這是用 Dragon 捕獲的一個(gè)非常簡單的交易。
在地址階段,CBE 0x3,這意味著“IO 寫入”。
它是地址0x00000000的 IO 寫入,數(shù)據(jù)0x0200。
FPGA 作為 PCI 邏輯分析儀
能夠看到總線運(yùn)行可能很有趣:更好地了解其操作。
檢查事務(wù)內(nèi)和事務(wù)之間的總線延遲。
進(jìn)行事后分析(如果您的 PCI 內(nèi)核存在功能問題)。
查看信號通常需要昂貴的設(shè)備,如總線擴(kuò)展器和邏輯分析儀。 這可能很棘手,因?yàn)镻CI規(guī)范不允許每個(gè)PCI信號(當(dāng)然每個(gè)PCI卡)上有一個(gè)以上的IO負(fù)載。 這是因?yàn)榭偩€對容性負(fù)載或線短截線很敏感,這些負(fù)載或短截線會使高速信號失真。
但是,F(xiàn)PGA不能像邏輯分析儀一樣工作嗎?
FPGA已經(jīng)連接到總線,并具有內(nèi)部存儲器,可用于實(shí)時(shí)捕獲總線操作。 Dragon 還有一個(gè) USB 接口,可用于轉(zhuǎn)儲 PCI 捕獲,而不會干擾 PCI 接口實(shí)現(xiàn),即使 PCI 總線“死機(jī)”。
FPGA 還可以輕松創(chuàng)建復(fù)雜的觸發(fā)條件,這些條件將比大多數(shù)邏輯分析儀更智能......如果要在地址 17x0 進(jìn)行第二次讀取后捕獲第 1234 次寫入,該怎么辦?
捕獲 PCI 信號
我們在這里構(gòu)建了一個(gè)“狀態(tài)”(=同步)邏輯分析器。捕獲的信號是:
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 個(gè)信號!
很好,如果我們選擇 3 個(gè)時(shí)鐘的深度,則非常適合 256 個(gè)塊。
實(shí)現(xiàn)起來很簡單:一旦設(shè)置了觸發(fā)條件,一個(gè) 8 位計(jì)數(shù)器開始為模塊提供信號,另一個(gè)計(jì)數(shù)器允許 USB 讀取模塊數(shù)據(jù)。 還添加了邏輯,以允許一定程度的預(yù)觸發(fā)采集 - Dragon 板文件中的詳細(xì)信息。
blockram 輸出按此順序多路復(fù)用至 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 批量讀取命令,采集數(shù)據(jù)并將其保存到“.pciacq”文件中以供進(jìn)一步分析。
PCI總線查看器
用于查看“.pciacq”文件的軟件可以在這里下載。包括一個(gè)示例“.pciacq”文件,該文件是此事務(wù)列表的結(jié)果捕獲:
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 ); |
該軟件如下所示: 一件有趣的事情:

在讀取周轉(zhuǎn)周期中,AD 總線顯示上一次讀取的數(shù)據(jù)......
PCI 4 - PCI 即插即用

我們的PCI卡還沒有在列表中...
配置空間
還記得PCI卡有三個(gè)“空間”嗎?內(nèi)存空間
IO 空間
配置空間
配置空間是PCI即插即用的核心。 操作系統(tǒng)(Windows、Linux等)首先讀取該信息,以查找是否插入了PCI卡及其特性。
對于簡單的電路板,配置空間僅包含 64 個(gè)字節(jié)。 它們的重要領(lǐng)域是:
抵消 | 名字 | 功能 | 注意 | 長度 |
---|---|---|---|---|
0 | 供應(yīng)商 ID | 指定生產(chǎn)商 | ...由 PCI-SIG 分配 | 2 字節(jié) |
2 | 設(shè)備 ID | 設(shè)備編號 | ...由制造商自己分配 | 2 字節(jié) |
4 | 命令 | 打開和關(guān)閉對PCI板的訪問 | ...但配置空間訪問始終處于打開狀態(tài) | 2 字節(jié) |
16 | BAR0(基址寄存器 0) | PCI板應(yīng)響應(yīng)的地址 | ...后跟 BAR1 到 BAR5 | 每個(gè) 4 個(gè)字節(jié) |
通過在這些位置實(shí)現(xiàn)正確的值和寄存器,操作系統(tǒng)可以“找到”PCI卡。
配置空間事務(wù)
每個(gè)PCI插槽都作為稱為IDSEL的信號。 IDSEL 信號不沿總線共享;每個(gè)PCI插槽都有自己的插槽。當(dāng) PCI 卡在總線上看到配置空間事務(wù),并且斷言其自己的 IDSEL 時(shí),它知道它應(yīng)該響應(yīng)。
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); |
之后,它可以是讀取或?qū)懭?,但它的工作方式與內(nèi)存或 IO 空間相同。
一些細(xì)節(jié):
對于供應(yīng)商 ID,我們只需選擇一個(gè)數(shù)字;我們只是在實(shí)驗(yàn),對吧?好的,0x0100工作正常。
設(shè)備 ID 可以保留為 0
命令位 0 是 IO 空間的“開/關(guān)”位,而位 1 是內(nèi)存空間的“開/關(guān)”位。
BAR0 是操作系統(tǒng)寫入的寄存器,一旦它決定 PCI 卡應(yīng)該位于哪個(gè)地址。
請參閱 PCI 規(guī)范/書籍,了解實(shí)際細(xì)節(jié)。
Windows 即插即用
實(shí)現(xiàn)這些寄存器后,操作系統(tǒng)可以發(fā)現(xiàn)新硬件。
但是操作系統(tǒng)需要驅(qū)動程序才能...

。它同意分配內(nèi)存資源。

PCI 5 - 適用于 Windows 的 PCI 軟件驅(qū)動程序
簡單的方法
簡單的方法就是讓別人為你做艱苦的工作!查看 WinDriver。
這是一個(gè)商業(yè)工具包,可以在幾分鐘內(nèi)為您構(gòu)建 PCI 即插即用驅(qū)動程序解決方案。
它的工作原理是這樣的:
運(yùn)行一個(gè)向?qū)頇z測您的即插即用設(shè)備,包括 PCI 卡。
您選擇您感興趣的卡,為您的設(shè)備命名并創(chuàng)建一個(gè)“.inf”文件。
這足以讓 Windows 能夠識別硬件并說服他應(yīng)該使用 WinDriver 的驅(qū)動程序。 退出向?qū)?,然后通過 Windows 的即插即用硬件檢測來安裝驅(qū)動程序。
安裝驅(qū)動程序后,再次運(yùn)行向?qū)?,這次是生成一些示例源代碼來訪問 PCI 卡。
Windriver 可能不錯(cuò),但 2000 美元,如果您只想嘗試 PCI 即插即用機(jī)制,那就太貴了。
艱難的道路
使用 Microsoft Windows DDK。
安裝 Windows DDK
最新的 Windows DDK 版本不是免費(fèi)的,而早期的化身 (98/2000) 可以免費(fèi)下載。DDK 易于安裝。 對于 Win98 和 Win2000 DDK,首先安裝 Visual C++ 5.0 或 6.0,然后安裝 DDK 本身。 然后按照“install.htm”說明使用“build”命令生成一些示例驅(qū)動程序。
最低 WDM 即插即用驅(qū)動程序
以下是 Windows 設(shè)備管理器分配 PCI 卡使用的內(nèi)存資源所需的最少代碼。由于它是一個(gè)WDM驅(qū)動程序,所以它可以在WinXP/2000/98中工作。
WDM 驅(qū)動程序的入口點(diǎn)是“DriverEntry”函數(shù)(類似于 C 程序的“main”)。
其主要目的是發(fā)布回調(diào)函數(shù)的地址。 我們的最低驅(qū)動程序只需要 2 個(gè)。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->DriverExtension->AddDevice = DevicePCI_AddDevice; DriverObject->MajorFunction[IRP_MJ_PNP] = DevicePCI_PnP; return STATUS_SUCCESS; } |
WDM 驅(qū)動程序至少創(chuàng)建一個(gè)“設(shè)備”(如果你的電腦有多個(gè)類似的項(xiàng)目,則同一 WDM 驅(qū)動程序可能會創(chuàng)建多個(gè)設(shè)備)。 在驅(qū)動程序可以創(chuàng)建設(shè)備之前,我們需要一個(gè)“設(shè)備擴(kuò)展”結(jié)構(gòu)。 每個(gè)設(shè)備都使用該結(jié)構(gòu)來存儲信息。 我們可以讓它變得盡可能大,一個(gè)典型的設(shè)備會在其中存儲許多字段。 我們的最小設(shè)備只需要一個(gè)字段。
typedef struct { PDEVICE_OBJECT NextStackDevice; } DevicePCI_DEVICE_EXTENSION, *PDevicePCI_DEVICE_EXTENSION; |
這個(gè)“NextStackDevice”是干什么用的?WDM實(shí)現(xiàn)細(xì)節(jié)...
WDM 設(shè)備處理 IRP(“I/O 請求數(shù)據(jù)包”、創(chuàng)建/讀取/寫入/關(guān)閉...... WDM 設(shè)備不是單獨(dú)工作的,而是組裝在設(shè)備的邏輯“堆?!敝?。 IRP 請求沿堆棧發(fā)送,并在途中進(jìn)行處理。 堆棧是從下到上創(chuàng)建的(底部=硬件層,頂部=邏輯層)。 創(chuàng)建堆棧時(shí),每個(gè)設(shè)備都會將自身附加到正下方的設(shè)備。 設(shè)備通常將有關(guān)設(shè)備的信息存儲在設(shè)備擴(kuò)展的正下方,以便以后可以轉(zhuǎn)發(fā) IRP 請求。 設(shè)備并不真正知道它在堆棧中的位置,它只是在請求到來時(shí)處理或轉(zhuǎn)發(fā)請求。
無論如何,現(xiàn)在我們可以實(shí)現(xiàn)DevicePCI_AddDevice。
它創(chuàng)建一個(gè)設(shè)備對象,并將設(shè)備附加到設(shè)備堆棧。
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 請求。
我們的最小設(shè)備僅處理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請求是我們接受或拒絕內(nèi)存資源的請求。 在這里,我們什么都不做,只是將請求向下轉(zhuǎn)發(fā)到堆棧中,在那里它總是被接受。

現(xiàn)在,我們的設(shè)備獲得了一些內(nèi)存資源,但對它們不做任何事情。
為了更有用,驅(qū)動程序需要:
在接受內(nèi)存資源之前檢查它們
導(dǎo)出設(shè)備名稱
實(shí)現(xiàn)一些“DeviceIOcontrol”以與 Win32 應(yīng)用程序通信
處理更多 IO 請求 (“IRP”)
...
評論