Windows驅動之端口與寄存器資源
通常對於CPU來說,外設都是通過讀寫設備上的寄存器來進行的,外設寄存器也稱爲“I/O端口”,而IO端口有兩種編址方式:獨立編址和統一編制。
對於統一編址來說,外設接口中的IO寄存器(即IO端口)與主存單元一樣看待,每個端口占用一個存儲單元的地址,將主存的一部分劃出來用作IO地址空間因此,統一編址也稱爲I/O內存方式。
對於獨立編址(單獨編址)來說,IO地址與存儲地址分開獨立編址,I/0端口地址不佔用存儲空間的地址範圍,這樣,在系統中就存在了另一種與存儲地址無關的IO地 址,CPU也必須具有專用與輸入輸出操作的IO指令(IN、OUT等)和控制邏輯;因此獨立編址也稱爲I/O端口方式。
爲了避免爲兼容各種平臺而使用大量條件編譯代碼,Windows NT的設計者發明了硬件抽象層(HAL),如下是用於訪問端口和內存寄存器的HAL函數
存取寬度 | 端口訪問函數 | 內存訪問函數 |
---|---|---|
8位 | READ_PORT_UCHAR WRITE_PORT_UCHAR |
READ_REGISTER_UCHAR WRITE_REGISTER_UCHAR |
16位 | READ_PORT_USHORT WRITE_PORT_USHORT |
READ_REGISTER_USHORT WRITE_REGISTER_USHORT |
32位 | READ_PORT_ULONG WRITE_PORT_ULONG |
READ_REGISTER_ULONG WRITE_REGISTER_ULONG |
8位字節串 | READ_PORT_BUFFER_UCHAR WRITE_PORT_BUFFER_UCHAR |
READ_REGISTER_BUFFER_UCHAR WRITE_REGISTER_BUFFER_UCHAR |
16位字串 | READ_PORT_BUFFER_USHORT WRITE_PORT_BUFFER_USHORT |
READ_REGISTER_BUFFER_USHORT WRITE_REGISTER_BUFFER_USHORT |
32位雙字串 | READ_PORT_BUFFER_ULONG WRITE_PORT_BUFFER_ULONG |
READ_REGISTER_BUFFER_ULONG WRITE_REGISTER_BUFFER_ULONG |
1. 端口資源
端口資源對應的是類型爲CmResourceTypePort
的資源,對於端口資源的提取代碼應該實現如下:
VOID OnStartDevice(...)
{
PHYSICAL_ADDRESS portbase; // base address of range
//...
for (ULONG i = 0; i < nres; ++i, ++resource)
{
switch (resource->Type)
{
case CmResourceTypePort:
portbase = resource->u.Port.Start;
pdx->nports = resource->u.Port.Length;
pdx->mappedport = (resource->Flags & CM_RESOURCE_PORT_IO) == 0;
break;
}
}
if (pdx->mappedport)
{
pdx->portbase = (PUCHAR)MmMapIoSpace(portbase, pdx->nports, MmNonCached);
}
else
{
pdx->portbase = (PUCHAR)portbase.QuadPart;
}
}
VOID StopDevice(...)
{
if (pdx->portbase && pdx->mappedport)
MmUnmapIoSpace(pdx->portbase, pdx->nports);
pdx->portbase = NULL;
}
其中:
- 資源描述符含有一個名爲
u
的聯合,u
聯合含有每個標準資源類型的子結構。u.Port
含有關於端口資源的信息。u.Port.Start
是I/O端口範圍的起始地址,起始地址是一個64位的PHYSICAL_ADDRESS
值u.Port.Length
是在該範圍內的端口數量。
- 端口資源的描述符有一個
Flags
成員,如果CPU支持分離的I/O地址空間,而這個給定端口又屬於這個空間,那麼這個標誌將有CM_RESOURCE_PORT_IO
設置。 - 如果沒有設置
CM_RESOURCE_PORT_IO
標誌,這可能是在一個Alpha平臺或其它RISC平臺上,你必須調用MmMapIoSpace
函數來獲得能訪問該端口的內核模式虛擬地址。訪問將真正使用內存引用,但在驅動程序中你仍要調用HAL中的PORT風格的例程(READ_PORT_UCHAR
)。 - 如果設置了
CM_RESOURCE_PORT_IO
標誌,這將在一個x86平臺上,因此你不必映射端口地址。所以,在你的驅動程序中你應該使用PORT風格的HAL例程訪問設備端口。HAL例程要求一個PUCHAR類型的端口地址參數,因此我們需要把基地址強制轉換成這個類型。QuadPart引用將使你得到一個與編譯平臺相適應的32位或64位指針。
2. 內存資源
內存資源對應的是類型爲CmResourceTypeMemory
的資源; 內存映射設備暴露了軟件可以使用load
和store
指令訪問的寄存器。
在IRP_MN_START_DEVICE中
從PnP管理器得到的轉換後的資源值是一個物理地址,你需要保留該物理地址對應的虛擬地址。轉換成虛擬地址以後,你將使用處理內存寄存器的HAL例程,如READ_REGISTER_UCHAR
和WRITE_REGISTER_UCHAR
,等等。這種資源的提取代碼如下:
VOID OnStartDevice(...)
{
PHYSICAL_ADDRESS membase; // base address of range
for (ULONG i = 0; i < nres; ++i, ++resource)
{
switch (resource->Type)
{
case CmResourceTypeMemory:
membase = resource->u.Memory.Start;
pdx->nbytes = resource->u.Memory.Length;
break;
}
}
pdx->membase = (PUCHAR)MmMapIoSpace(membase, pdx->nbytes, MmNonCached);
}
VOID StopDevice(...)
{
if (pdx->membase)
MmUnmapIoSpace(pdx->membase, pdx->nbytes);
pdx->membase = NULL;
}
- 在資源描述符中
u.Memory
含有內存資源信息。u.Memory.Start
是一個內存範圍的起始地址,起始地址是一個64位的PHYSICAL_ADDRESS
值。u.Memory.Length
是該範圍的字節長度
- 調用
MmMapIoSpace
函數獲得一個內核模式虛擬地址,這樣內存範圍才能被訪問。