Windows驅動之端口與寄存器資源

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;
}

其中:

  1. 資源描述符含有一個名爲u的聯合,u聯合含有每個標準資源類型的子結構。
    • u.Port含有關於端口資源的信息。
    • u.Port.Start是I/O端口範圍的起始地址,起始地址是一個64位的PHYSICAL_ADDRESS
    • u.Port.Length是在該範圍內的端口數量。
  2. 端口資源的描述符有一個Flags成員,如果CPU支持分離的I/O地址空間,而這個給定端口又屬於這個空間,那麼這個標誌將有CM_RESOURCE_PORT_IO設置。
  3. 如果沒有設置CM_RESOURCE_PORT_IO標誌,這可能是在一個Alpha平臺或其它RISC平臺上,你必須調用MmMapIoSpace函數來獲得能訪問該端口的內核模式虛擬地址。訪問將真正使用內存引用,但在驅動程序中你仍要調用HAL中的PORT風格的例程(READ_PORT_UCHAR)。
  4. 如果設置了CM_RESOURCE_PORT_IO標誌,這將在一個x86平臺上,因此你不必映射端口地址。所以,在你的驅動程序中你應該使用PORT風格的HAL例程訪問設備端口。HAL例程要求一個PUCHAR類型的端口地址參數,因此我們需要把基地址強制轉換成這個類型。QuadPart引用將使你得到一個與編譯平臺相適應的32位或64位指針。

2. 內存資源

內存資源對應的是類型爲CmResourceTypeMemory的資源; 內存映射設備暴露了軟件可以使用loadstore指令訪問的寄存器。

IRP_MN_START_DEVICE中從PnP管理器得到的轉換後的資源值是一個物理地址,你需要保留該物理地址對應的虛擬地址。轉換成虛擬地址以後,你將使用處理內存寄存器的HAL例程,如READ_REGISTER_UCHARWRITE_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;
}
  1. 在資源描述符中
    • u.Memory含有內存資源信息。
    • u.Memory.Start是一個內存範圍的起始地址,起始地址是一個64位的PHYSICAL_ADDRESS值。
    • u.Memory.Length是該範圍的字節長度
  2. 調用MmMapIoSpace函數獲得一個內核模式虛擬地址,這樣內存範圍才能被訪問。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章