PCIe设备漫游记----寄存器读写篇

      上篇中,我们设备打开函数已经得到了我们PCIe设备的句柄了。接下来我们来看看,设备打开之后,上层软件是怎样利用该句柄实现对设备上具体寄存器的访问的。

1:寄存器写操作

上层应用程序写操作函数代码:

/********************************************************************/
/*			    Write register 32bits		    */
/********************************************************************/
DLLEXP int CCONV ClLib_RegWrite32( HANDLE hHandle, unsigned char bar, unsigned long offset, unsigned long data )
{
	PORT_ACCESS port;
	DWORD       dwBytes;
	int         status = RTN_OK;

	if( (hHandle == NULL) || (hHandle == INVALID_HANDLE_VALUE) || (bar >= MAX_PCI_BAR) )
	{
		return (RTN_PRM_ERR);
	}

	memset(&port,0,sizeof(PORT_ACCESS));
	port.bar	 = bar;
	port.offs	 = offset;
	port.u.ldata = data;

	if(!DeviceIoControl(hHandle, IOCTL_WRITE_BASE_ULONG, 
							&port, sizeof(PORT_ACCESS),
							NULL, 0,
							&dwBytes, NULL))
	{
		status = RTN_ERR;
	}
	
	return (status);
}

函数中,第一个参数hHandle就是我们上篇中通过设备打开函数得到的设备句柄,参数bar 和 offset 分别指定寄存器所处的BAR空间编号以及对应的偏移地址。代码中重点就是DeviceIoControl函数。下面我们来详细探讨。
BOOL WINAPI DeviceIoControl(
  __in         HANDLE hDevice,
  __in         DWORD dwIoControlCode,
  __in_opt     LPVOID lpInBuffer,
  __in         DWORD nInBufferSize,
  __out_opt    LPVOID lpOutBuffer,
  __in         DWORD nOutBufferSize,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped
);
HANDLE hDevice: 设备句柄

DWORD dwIoControlCode: 控制代码

LPVOID lpInBuffer: 输入Buffer

DWORD nInBufferSize: 输入Buffer大小

LPVOID lpOutBuffer: 输出Buffer

DWORD nOutBufferSize:输出Buffer大小

LPDWORD lpBytesReturned: 返回的数据大小(存放于输出Buffer中)

LPOVERLAPPED lpOverlapped: 用于指定该I/O操作是异步还是同步。

本例中,我们的读写控制代码(上述函数的第二个参数)定义如下。

/* Used 32768-65535 */
#define FILE_DEVICE_DEMOPCI 		530710
#define CODE_BASE          		0x0A00
/* Control code definition */
#define IOCTL_WRITE_BASE_ULONG		CTL_CODE(FILE_DEVICE_DEMOPCI, CODE_BASE+ 1 , METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_READ_BASE_ULONG		CTL_CODE(FILE_DEVICE_DEMOPCI, CODE_BASE+ 2 , METHOD_BUFFERED, FILE_READ_ACCESS)

另外。我们还定义了一个PORT_ACCESS结构体来当做输入Buffer,由于写操作不涉及取回数据的问题,我们将输出Buffer和其大小分别生成NULL和0即可。下面是PORT_ACCESS结构体的详细定义: 

/* For Single Access */
typedef struct _tagPortARG
{
	unsigned char bar;         /* Pci BaseAddress number */
	unsigned long offs;        /* offset */
  	union 
  	{
      		unsigned long  ldata;  /* send/recv data buffer */
      		unsigned short sdata;  /* send/recv data buffer */
      		unsigned char  cdata;  /* send/recv data buffer */
  	}u;
} PORT_ACCESS ,*PPORT_ACCESS;
该结构体中,我们把要操作的寄存器对应BAR空间编号以及偏移地址还有要写入的数据填入该结构中,然后调用DeviceIoControl函数,I/O管理器将创建一个主功能号为IRP_MJ_DEVICE_CONTROL的IRP包传递给我们底层的驱动程序,而驱动将会调用我们之前在DriverEntry函数里面已经注册好的派遣函数DEMOPciDevcieControl。
NTSTATUS DEMOPciDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp)
{
	NTSTATUS			status = STATUS_SUCCESS;
	PTSTDPCI_DEVICE_EXT		pDevExt;
	PIO_STACK_LOCATION		pIrpStack;
	void * pBuffer;
        ULONG Size;
    
        LARGE_INTEGER StartTime, EndTime, Freq;
        LONGLONG  IntervelTime;
		
	pDevExt = (PDEMOPCI_DEVICE_EXT)DeviceObject->DeviceExtension;
	/* Flag setting when driver is being used */
	DEMOPciRequestIncrement(pDevExt);
	
	if (!pDevExt->Started) 
	{
		status = STATUS_DEVICE_NOT_READY;
		pIrp->IoStatus.Status = STATUS_DEVICE_NOT_READY;
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
	}
	
	pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
	
	switch (pIrpStack->MajorFunction)
	{
	case IRP_MJ_DEVICE_CONTROL:
		switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode)
		{
			case IOCTL_WRITE_BASE_ULONG:
				status = DEMOPciWriteBaseUlong(pDevExt, pIrp);
				break;
			case IOCTL_READ_BASE_ULONG:
				status = DEMOPciReadBaseUlong(pDevExt, pIrp);
				break;
			case IOCTL_CMN_BUFF_ALLOC:
				status = DEMOPciCommonBufferAlloc(pDevExt, pIrp);
				break;
			case IOCTL_CMN_BUFF_FREE:
				status = DEMOPciCommonBufferFree(pDevExt, pIrp);
				break;
                        ..............
                        ..............
                        ..............

                        case IOCTL_CREATE_EVENT:
				status= DEMOPciCreateEvent(pDevExt, pIrp);
				break;	
			case IOCTL_CLOSE_EVENT:
				status= DEMOPciCloseEvent(pDevExt, pIrp);
				break;
			default:
				status = STATUS_INVALID_PARAMETER;
				break;
		}
		break;
	default:
		status = STATUS_NOT_IMPLEMENTED;
		break;
	}
        break;
    default:
        status = STATUS_NOT_IMPLEMENTED;
        break;
    }
    
    pIrp->IoStatus.Status = status;
    if (status != STATUS_PENDING) 
    {
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        /* Flag release when driver is being used */
        DEMOPciRequestDecrement(pDevExt);    
    }
    

    return (status);
}

我们看到在DEMOPciDevcieControl函数中,根据不同的设备控制码,将执行不同的I/O操作(如DMA Buffer空间的申请与释放,事件创建与关闭等等)。本例中我们只讨论寄存器读写函数的具体实现。
NTSTATUS DEMOPciWriteBaseUlong( PDEMOPCI_DEVICE_EXT	pDevExt, PIRP pIrp ) 
{
	UCHAR         	        bar;
	NTSTATUS	        status = STATUS_SUCCESS;
	PIO_STACK_LOCATION	pIrpStack;
	PORT_ACCESS		*pBuffer;
	ULONG		        ulInBufferSize, ulOutBufferSize;
	PULONG			pulIoAddr;

	pIrpStack       = IoGetCurrentIrpStackLocation(pIrp);          
        ulInBufferSize	= pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	pBuffer		= (PORT_ACCESS *)pIrp->AssociatedIrp.SystemBuffer;
	bar = pBuffer->bar;
	if(bar < 6)
	{
		if( pDevExt->base[bar].WhichMapped == TYPE_MEM )
		{                        
                        DebugPrint("TYPE_MEM\n");
                        if( ( (pBuffer->offs) + sizeof(ULONG) ) > ( pDevExt->base[bar].MemorySize ) )
			{
				status=STATUS_INVALID_PARAMETER;
			}
			else
			{
                                DebugPrint("base[%d].MemoryMappedAddress+(pBuffer->offs): %x\n",bar, ((PUCHAR)pDevExt->base[bar].MemoryMappedAddress+(pBuffer->offs)));
				DebugPrint("pBuffer->u.ldata: %x\n",pBuffer->u.ldata);
				WRITE_REGISTER_ULONG((PULONG)((PUCHAR)pDevExt->base[bar].MemoryMappedAddress+(pBuffer->offs)), pBuffer->u.ldata);
			}
		}
		else if( pDevExt->base[bar].WhichMapped == TYPE_IO )
		{
			DebugPrint("TYPE_IO\n");
			if( ( (pBuffer->offs) + sizeof(ULONG) ) > ( pDevExt->base[bar].IoPortSize ) )
			{
				status=STATUS_INVALID_PARAMETER;
			}
			else
			{
				DebugPrint("base[%d].IoPortMappedAddress+(pBuffer->offs): %x\n",bar, ((PUCHAR)pDevExt->base[bar].IoPortMappedAddress+(pBuffer->offs)));
				DebugPrint("pBuffer->u.ldata: %x\n",pBuffer->u.ldata);
				WRITE_PORT_ULONG((PULONG)((PUCHAR)pDevExt->base[bar].IoPortMappedAddress+(pBuffer->offs)), pBuffer->u.ldata);
			}
		}
		else
		{
			status=STATUS_UNSUCCESSFUL;
		}
	}
	else
	{
		status=STATUS_INVALID_PARAMETER;
	}

	if(status == STATUS_SUCCESS)
	{
		pIrp->IoStatus.Information = 4;
	}
	else
	{
		pIrp->IoStatus.Information = 0;
	}

	return (status);
}

上述代码中,函数通过解析IRP包中的信息,得到要操作的寄存器的bar空间编号,偏移地址,以及要写入的数据等信息,然后调用系统函数WRITE_REGISTER_ULONG完成对寄存器的写操作,操作成功之后,该函数通过设置pIrp->IoStatus.Information = 4 来表明此次IRP操作的字节数,然后返回状态给DEMOPciDevcieControl函数,由DEMOPciDevcieControl函数调用IoCompleteRequest函数来完成本次IRP操作,最后I/O控制器将结果返回给上层函数,至此,寄存器的写操作已经成功完成了。


2:寄存器读操作

知道了写操作的流程,读操作就依葫芦画瓢,非常简单了。在此我们就不详细叙述。代码就是最好的老师,我们直接展示。

上层寄存器读操作函数如下:

/********************************************************************/
/*			    Read register 32bits		    */
/********************************************************************/
DLLEXP int CCONV ClLib_RegRead32( HANDLE hHandle, unsigned char bar, unsigned long offset, unsigned long *data )
{
	PORT_ACCESS port;
	DWORD       dwBytes;
	int         status = RTN_OK;

	if( (hHandle == NULL) || (hHandle == INVALID_HANDLE_VALUE) || (bar >= MAX_PCI_BAR) )
	{
		return (RTN_PRM_ERR);
	}

	memset(&port,0,sizeof(PORT_ACCESS));
	port.bar	 = bar;
	port.offs	 = offset;

	if(!DeviceIoControl(hHandle, IOCTL_READ_BASE_ULONG, 
							&port, sizeof(PORT_ACCESS),
							&port, sizeof(PORT_ACCESS),
							&dwBytes, NULL))
	{
		status = RTN_ERR;
	}
	*data = port.u.ldata;
	
	return (status);
}

驱动层读函数如下:

NTSTATUS DEMOPciReadBaseUlong( PDEMOPCI_DEVICE_EXT pDevExt, PIRP pIrp ) 
{
	NTSTATUS				status = STATUS_SUCCESS;
	PIO_STACK_LOCATION	pIrpStack;
	PORT_ACCESS			*pBuffer;
	ULONG					ulInBufferSize, ulOutBufferSize;

	pIrpStack       	= IoGetCurrentIrpStackLocation(pIrp);
	ulInBufferSize	= pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	ulOutBufferSize	= pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
	pBuffer		= (PORT_ACCESS *)pIrp->AssociatedIrp.SystemBuffer;		
	bar = pBuffer->bar;
	if(bar < 6)
	{
		pBuffer->u.ldata=0;
		if( pDevExt->base[bar].WhichMapped == TYPE_MEM )
		{
			DebugPrint("TYPE_MEM\n");
			if( ( (pBuffer->offs) + sizeof(ULONG) ) > ( pDevExt->base[bar].MemorySize ) )
			{
				status=STATUS_INVALID_PARAMETER;
			}
			else
			{
				DebugPrint("base[%d].MemoryMappedAddress+(pBuffer->offs): %x\n",bar,	((PUCHAR)pDevExt->base[bar].MemoryMappedAddress+(pBuffer->offs)));
				DebugPrint("pBuffer->u.ldata: %x\n",pBuffer->u.ldata);
				DebugPrint("pDevExt->base[bar].MemorySize = %lx\n", pDevExt->base[bar].MemorySize);
				pBuffer->u.ldata = READ_REGISTER_ULONG( (PULONG)((PUCHAR)pDevExt->base[bar].MemoryMappedAddress+(pBuffer->offs)) );
			}
		}
		else if( pDevExt->base[bar].WhichMapped == TYPE_IO )
		{
			DebugPrint("TYPE_IO\n");
			if( ( (pBuffer->offs) + sizeof(ULONG) ) > ( pDevExt->base[bar].IoPortSize ) )
			{
				status=STATUS_INVALID_PARAMETER;
			}
			else
			{
				DebugPrint("base[%d].IoPortMappedAddress+(pBuffer->offs): %x\n",bar, ((PUCHAR)pDevExt->base[bar].IoPortMappedAddress+(pBuffer->offs)));
				DebugPrint("pBuffer->u.ldata: %x\n",pBuffer->u.ldata);
				pBuffer->u.ldata = READ_PORT_ULONG( (PULONG)((PUCHAR)pDevExt->base[bar].IoPortMappedAddress+(pBuffer->offs)) );
			}
		}
		else
		{
			status=STATUS_UNSUCCESSFUL;
		}
	}
	else
	{
		status=STATUS_INVALID_PARAMETER;
	}
	if(status == STATUS_SUCCESS)
		pIrp->IoStatus.Information = sizeof(PORT_ACCESS);
	else
		pIrp->IoStatus.Information = 0;

	return (status);
}


你可能感兴趣的:(PCIe设备漫游记)