Windows驱动之端口与寄存器资源

文章目录

  • Windows驱动之端口与寄存器资源
    • 1. 端口资源
    • 2. 内存资源

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函数获得一个内核模式虚拟地址,这样内存范围才能被访问。

你可能感兴趣的:(Windows驱动开发)