DriverObject->DriverExtension->AddDevice = USB2COM_PnPAddDevice;
(1) IoCreateDevice系统API的原理为:
NTKERNELAPI NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject, IN ULONG DeviceExtensionSize, IN PUNICODE_STRING DeviceName OPTIONAL, IN DEVICE_TYPE DeviceType, IN ULONG DeviceCharacteristics, IN BOOLEAN Reserved, OUT PDEVICE_OBJECT *DeviceObject );
在之前真实的USB驱动中我们是这样创建的:
ntStatus = IoCreateDevice( DriverObject, // our driver object sizeof(DEVICE_EXTENSION), // extension size for us NULL, // name for this device FILE_DEVICE_UNKNOWN, FILE_AUTOGENERATED_DEVICE_NAME, // device characteristics FALSE, // Not exclusive &deviceObject); // Our device object
就是第三个参数为NULL, 第四个参数为FILE_DEVICE_UNKNOWN,意味着我们驱动想附加的设备是空的,且未知。
由于我们是创建虚拟串口驱动,因此调用IoCreateDevice创建时在指定串口设备的名字,且指定设备类型。
ntStatus = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), &deviceObjName, FILE_DEVICE_SERIAL_PORT, FILE_DEVICE_SECURE_OPEN, TRUE, DeviceObject);
(2)为自定义的扩展设备中的设备名字段指定设备名
// deviceExtension->DeviceName为UNICODE_STRING类型 RtlZeroMemory(&deviceExtension->DeviceName, sizeof(UNICODE_STRING)); deviceExtension->DeviceName.MaximumLength = deviceObjName.Length + sizeof(WCHAR); // Buffer重新分配 deviceExtension->DeviceName.Buffer = USB2COM_ExAllocatePool(NonPagedPool, deviceObjName.Length + sizeof(WCHAR)); RtlZeroMemory(deviceExtension->DeviceName.Buffer, deviceObjName.Length+sizeof(WCHAR)); RtlAppendUnicodeStringToString(&deviceExtension->DeviceName, &deviceObjName);
(3)初始化事件、串口控件对象、关键代码段、自旋锁、读写队列链表。
// this event is triggered when there is no pending io of any kind and device is removed KeInitializeEvent(&deviceExtension->RemoveEvent, NotificationEvent, FALSE); // this event is triggered when self-requested power irps complete KeInitializeEvent(&deviceExtension->SelfRequestedPowerIrpEvent, NotificationEvent, FALSE); // this event is triggered when there is no pending io (pending io count == 1 ) KeInitializeEvent(&deviceExtension->NoPendingIoEvent, NotificationEvent, FALSE); // spinlock used to protect inc/dec iocount logic KeInitializeSpinLock (&deviceExtension->IoCountSpinLock); deviceExtension->BaudRate = 19200; /* Set line control */ deviceExtension->SerialLineControl.StopBits = STOP_BIT_1; deviceExtension->SerialLineControl.Parity = NO_PARITY; deviceExtension->SerialLineControl.WordLength = 8; deviceExtension->SpecialChars.XonChar = SERIAL_DEF_XON; deviceExtension->SpecialChars.XoffChar = SERIAL_DEF_XOFF; deviceExtension->HandFlow.ControlHandShake = SERIAL_DTR_CONTROL; deviceExtension->HandFlow.FlowReplace = SERIAL_RTS_CONTROL; deviceExtension->HandFlow.XoffLimit = 300; deviceExtension->HandFlow.XonLimit = 100; InitializeCircularBuffer(&deviceExtension->InputBuffer, 512); InitializeCircularBuffer(&deviceExtension->OutputBuffer, 512); KeInitializeSpinLock(&deviceExtension->InputBufferLock); KeInitializeSpinLock(&deviceExtension->OutputBufferLock); InitializeListHead(&deviceExtension->ReadQueue); KeInitializeSpinLock(&deviceExtension->ReadQueueSpinLock); InitializeListHead(&deviceExtension->WriteQueue); KeInitializeSpinLock(&deviceExtension->WriteQueueSpinLock); InitializeListHead(&deviceExtension->PurgeQueue); KeInitializeSpinLock(&deviceExtension->PurgeQueueSpinLock);
设置DO_POWER_PAGABLE的目的是在suspend期间不接收一个IRP_MN_STOP_DEVICE,
在resume时不接收一个IRP_MN_START_DEVICE消息。
// we support direct io for read/write // deviceObject->Flags |= DO_DIRECT_IO; //Set this flag causes the driver to not receive a IRP_MN_STOP_DEVICE //during suspend and also not get an IRP_MN_START_DEVICE during resume. //This is neccesary because during the start device call, // the GetDescriptors() call will be failed by the USB stack. deviceObject->Flags |= DO_POWER_PAGABLE;
deviceExtension->TopOfStackDeviceObject = IoAttachDeviceToDeviceStack(deviceObject, PhysicalDeviceObject);
status = IoRegisterDeviceInterface(PDevExt->PhysicalDeviceObject, (LPGUID)&GUID_CLASS_COMPORT, NULL, &PDevExt->DeviceClassSymbolicName);
(1)建立IRP来产生一个发往FDO的内部查询请求;
irp = IoAllocateIrp(LowerDeviceObject->StackSize, FALSE);
(2)设置IRP要发往的设备栈Location(是更低层的设备,在这里就是它附加下的USB)的信息,
eg: MajorFunction、MinorFunction、Parameters.DeviceCapabilities.Capabilities
nextStack = IoGetNextIrpStackLocation(irp); nextStack->MajorFunction= IRP_MJ_PNP; nextStack->MinorFunction= IRP_MN_QUERY_CAPABILITIES;
在以上代码中的IoGetNextIrpStackLocation是一个宏,它的定义如下:
#define IoGetNextIrpStackLocation( Irp ) (\ (Irp)->Tail.Overlay.CurrentStackLocation - 1 )
从以上宏可以看出:IRP结构体中存有当前的栈Location CurrentStackLoctation,而我们的IRP要发往的栈Location的获得方法就是原有栈的地址 - 1。
(3)设置IRP的完成例程;(在完成全程中就是把事件激活,这样KeWaitForSingleObject就能走下来)
(4)把IRP发送下去,并等待完成;
ntStatus = IoCallDriver(LowerDeviceObject, irp); USB2COM_KdPrint( DBGLVL_MEDIUM,(" USB2COM_QueryCapabilities() ntStatus from IoCallDriver to PCI = 0x%x\n", ntStatus)); if (ntStatus == STATUS_PENDING) { // wait for irp to complete KeWaitForSingleObject( &event, Suspended, KernelMode, FALSE, NULL);
当完成全程返回时, nextStack->Parameters.DeviceCapabilities.Capabilities = DeviceCapabilities;指针就存着我们的性能信息。
直接调用系统API:
USBD_GetUSBDIVersion(&versionInformation);
DriverObject->MajorFunction[IRP_MJ_PNP] = USB2COM_ProcessPnPIrp;
在USB2COM_ProcessPnPIrp里case了以下几个消息:
IRP_MN_START_DEVICE 、IRP_MN_QUERY_STOP_DEVICE、IRP_MN_CANCEL_STOP_DEVICE、IRP_MN_STOP_DEVICE、
IRP_MN_QUERY_REMOVE_DEVICE、IRP_MN_CANCEL_REMOVE_DEVICE、IRP_MN_SURPRISE_REMOVAL、IRP_MN_REMOVE_DEVICE
文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中讲到的同类派遣函数,它只case 了以下:
IRP_MN_START_DEVICE、
IRP_MN_STOP_DEVICE 、IRP_MN_EJECT和IRP_MN_SURPRISE_REMOVAL
所以一比较,觉得USB2COM考虑得更全面。
(1)IRP_MN_START_DEVICE
与文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中的处理基本类似;
(2)IRP_MN_QUERY_STOP_DEVICE
与文章<<Window XP驱动开发(九) USB WDM驱动开发实例 bulkusb >>中的处理基本类似;
(3)IRP_MN_CANCEL_STOP_DEVICE
(4)IRP_MN_STOP_DEVICE、
(5)IRP_MN_QUERY_REMOVE_DEVICE、
(6)IRP_MN_CANCEL_REMOVE_DEVICE、
(7)IRP_MN_SURPRISE_REMOVAL、
(8)IRP_MN_REMOVE_DEVICE
DriverObject->MajorFunction[IRP_MJ_CREATE] = USB2COM_Create;
如果不能接收一个新的IO请求,那么直接返回。
if ( !USB2COM_CanAcceptIoRequests( DeviceObject ) ) { ntStatus = STATUS_DELETE_PENDING; USB2COM_KdPrint( DBGLVL_DEFAULT,("ABORTING USB2COM_Create\n")); goto done; }
在以下的条件不能接收一个新的IO(判断的标志是我们自己标记的):
1) 设备已经被移除了,
2) 从来没有被启动过,,
3) 已经停止了,
4) 有一个移除的请求还没处理,
5) 有一个停止的请求还没处理。
//flag set when processing IRP_MN_REMOVE_DEVICE if ( !deviceExtension->DeviceRemoved && // device must be started( enabled ) deviceExtension->DeviceStarted && // flag set when driver has answered success to IRP_MN_QUERY_REMOVE_DEVICE !deviceExtension->RemoveDeviceRequested && // flag set when driver has answered success to IRP_MN_QUERY_STOP_DEVICE !deviceExtension->StopDeviceRequested ){ fCan = TRUE; }
StartReadIntUrb( DeviceObject, &interface->Pipes[0] );
(1)如果判断现在不能接收IO请求,那么就记录在extension中的IRP置为完成状态,然后返回;
(2)分配IRP、URB空间,并存到Extension->ReadIntUrbs数组中,再把Pipe句柄、Buffer、传送标志填入到URB结构体成员;
irp = IoAllocateIrp(stackSize, FALSE); if(irp == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } urb = USB2COM_ExAllocatePool(NonPagedPool, sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)); if(urb == NULL) { IoFreeIrp(irp); return STATUS_INSUFFICIENT_RESOURCES; } deviceExtension->ReadIntUrbs[i].Irp = irp; deviceExtension->ReadIntUrbs[i].Urb = urb; deviceExtension->ReadIntUrbs[i].deviceObject = DeviceObject; deviceExtension->ReadIntUrbs[i].PipeInfo = PipeInfo; InitIntUrb(urb, PipeInfo->PipeHandle, deviceExtension->ReadIntUrbs[i].TransferBuffer, sizeof(deviceExtension->ReadIntUrbs[i].TransferBuffer), TRUE);
InitIntUrb为自己封装的函数,很简单,就是把数据填入到urb结构体中:
VOID InitIntUrb( IN PURB urb, IN USBD_PIPE_HANDLE PipeHandle, IN PUCHAR TransferBuffer, IN ULONG length, IN BOOLEAN Read ) { USHORT siz = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER); if (urb) { RtlZeroMemory(urb, siz); urb->UrbBulkOrInterruptTransfer.Hdr.Length = (USHORT) siz; urb->UrbBulkOrInterruptTransfer.Hdr.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; urb->UrbBulkOrInterruptTransfer.PipeHandle = PipeHandle; urb->UrbBulkOrInterruptTransfer.TransferFlags = Read ? USBD_TRANSFER_DIRECTION_IN : 0; // short packet is not treated as an error. urb->UrbBulkOrInterruptTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK; // // not using linked urb's // urb->UrbBulkOrInterruptTransfer.UrbLink = NULL; urb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL; urb->UrbBulkOrInterruptTransfer.TransferBuffer = TransferBuffer; urb->UrbBulkOrInterruptTransfer.TransferBufferLength = length; } }
(3)初始化IRP的栈Location及它的完成例程ReadIntUrbComplete,并把当前ReadIntUrbs的内存作为完成例程的Context;
(4)完成例程ReadIntUrbComplete的处理;
A、判断返回执行后返回的IRP、URB的状态,如果为未连接或取消那么就释放IRP及URB内存,并完成把extension中的IRP置为完成状态,然后直接返回;否则往下执行;
B、把IRP之前绑定的Buffer数据拷到InputBuffer中(现在的Buffer就是我们的结果数据);
KeAcquireSpinLock(&deviceExtension->InputBufferLock, &oldIrql); PushCircularBufferEntry( &deviceExtension->InputBuffer, &pIntUrbs->TransferBuffer[1], pIntUrbs->TransferBuffer[0]); KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql);
PushCircularBufferEntry为自己封装的函数:
把data内存中的len个数据拷到pBuffer中
NTSTATUS PushCircularBufferEntry( IN PCIRCULAR_BUFFER pBuffer, IN PUCHAR data, IN ULONG len) { ULONG NextPosition; DbgPrint("Serial: PushCircularBufferEntry(data %p, len %d)\n", data, len); ASSERT(pBuffer); ASSERT(pBuffer->Length); if ((data == NULL) || (len == 0)) return STATUS_INVALID_PARAMETER; do{ NextPosition = (pBuffer->WritePosition + 1) % pBuffer->Length; if (NextPosition == pBuffer->ReadPosition) return STATUS_BUFFER_TOO_SMALL; pBuffer->Buffer[pBuffer->WritePosition] = *data++; pBuffer->DataLen++; pBuffer->WritePosition = NextPosition; }while(--len); return STATUS_SUCCESS; }
C、如果extension中有等待的IRP,且等待的事件中有SERIAL_EV_RXCHAR,那么通过SerialCompleteCurrentWait完成等待串口的等待IRP。
C、1 把当前IRP的取消完成例程置为NULL,根据包是否已经被取消标志pIrp->Cancel 及之前的IRP取消例程是否被执行过了,那么调用SerialCancelCurrentWait来取消。
注意SerialCancelCurrentWait中很重要的是要通过调用IoReleaseCancelSpinLock来释放系统的删除自旋锁。
void SerialCancelCurrentWait( PDEVICE_OBJECT DeviceObject, PIRP pIrp ) { PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION deviceExtension; DbgPrint("SerialCancelCurrentWait Enter Irp = %p\n",pIrp); ASSERT(pIrp); irpStack = IoGetCurrentIrpStackLocation(pIrp); deviceExtension = irpStack->DeviceObject->DeviceExtension; deviceExtension->CurrentWaitIrp = NULL; /* *All Cancel routines must follow these guidelines: * 1. Call IoReleaseCancelSpinLock to release the system's cancel spin lock * 2. ... */ IoReleaseCancelSpinLock(pIrp->CancelIrql); pIrp->IoStatus.Status = STATUS_CANCELLED; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); DbgPrint("SerialCancelCurrentWait Exit\n"); }
C、2 如果没有被取消,那么把当前我们的事件返回给应用层,并完成IRP。
deviceExtension->CurrentWaitIrp = NULL; deviceExtension->HistoryMask &= ~events; IoReleaseCancelSpinLock(OldIrql); pIrp->IoStatus.Information = sizeof(ULONG); pIrp->IoStatus.Status = ntStatus; *((ULONG *)pIrp->AssociatedIrp.SystemBuffer) = events; IoCompleteRequest (pIrp,IO_NO_INCREMENT);
D、完成当前的读IRP;
D、1 如果有当前读的Irp,那么把第(4)B、里得到的结果数据弹出到当前的读IRP中(通过当前读的IRP中的MdlAddress地址访问到内存)。
if(deviceExtension->CurrentReadIrp) { ULONG haveLen; BOOLEAN returnWhatsPresent = FALSE; if(deviceExtension->SerialTimeOuts.ReadIntervalTimeout && ( deviceExtension->SerialTimeOuts.ReadTotalTimeoutMultiplier == 0) && ( deviceExtension->SerialTimeOuts.ReadTotalTimeoutConstant == 0) ) { returnWhatsPresent = TRUE; } ioBuffer = MmGetSystemAddressForMdlSafe(deviceExtension->CurrentReadIrp->MdlAddress,NormalPagePriority ); ioLength = MmGetMdlByteCount(deviceExtension->CurrentReadIrp->MdlAddress); KeAcquireSpinLock(&deviceExtension->InputBufferLock, &oldIrql); haveLen = CircularBufferDataLen(&deviceExtension->InputBuffer); if( (ioLength <= haveLen) || (returnWhatsPresent && (haveLen > 0))) { ioLength = (ioLength < haveLen) ? ioLength : haveLen; DbgPrint("Complete CurrentReadIrp ioLength = %d\n",ioLength); ntStatus = PopCircularBufferEntry(&deviceExtension->InputBuffer,ioBuffer,ioLength); KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql); deviceExtension->CurrentReadIrp->IoStatus.Information = ioLength; deviceExtension->CurrentReadIrp->IoStatus.Status = ntStatus; IoCompleteRequest(deviceExtension->CurrentReadIrp,IO_NO_INCREMENT); deviceExtension->CurrentReadIrp = DequeueReadIrp(deviceExtension); } else KeReleaseSpinLock(&deviceExtension->InputBufferLock, oldIrql); }
以上代码中MmGetSystemAddressForMdlSafe
// 函数说明: // 此函数返回MDL映射的地址,如果此MDL还没有被映射,那么它将会被映射 // 参数: // MemoryDescriptorList - 指向MDL的指针 // Priority - 指向一个标志,该标记表明它是如何的重要,以至于这个请求在低的可用PTE条件下也成功了 // 返回值: // 返回映射页的基地址,这个基地址和MDL的虚拟地址有同样的偏移地址. // 与MmGetSystemAddressForMdl不同,在失败时它会返回NULL,而不是bugchecking the system. // 版本说明: // 此宏在WDM 1.0中不同用,在WDM1.0的驱动中实现此功能是通过提供同步并set/reset MDL_MAPPING_CAN_FAIL bit. #define MmGetSystemAddressForMdlSafe(MDL, PRIORITY) \ (((MDL)->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | \ MDL_SOURCE_IS_NONPAGED_POOL)) ? \ ((MDL)->MappedSystemVa) : \ (MmMapLockedPagesSpecifyCache((MDL), \ KernelMode, \ MmCached, \ NULL, \ FALSE, \ (PRIORITY))))
第一个参数是deviceExtension->CurrentReadIrp->MdlAddress,我们从wdm.h可以看到它的定义:
// 定义一个指向这个I/O请求的内存描述符(MDL)的指针, // 此域仅在I/O是“direct I/O"时被用 PMDL MdlAddress;
D、2 判断是否能接收一个新的IO请求
如果不能接收一个新的IO请求或者Pipe[0]关闭了,那么直接返回。
D、3 循环发送IRP,并置完成例程为ReadIntUrbComplete。(这样,就又回到了(4)),何时结束呢?
结束条件:直至设备不能接收一个新的IO请求或未连接、取消。
所以处理当前读IRP是一直进行的。
PrepareWriteIntUrb( IN PDEVICE_OBJECT DeviceObject, IN PUSBD_PIPE_INFORMATION PipeInfo )
A、分配写的Irb 与 Urb空间。
B、保存Pipe[1]地址为deviceExtension->WriteIntUrb.PipeInfo存在extension中。
应用程序的名字从当前IRP中的fileObject得到。
ourPipeInfo = USB2COM_PipeWithName( DeviceObject, &fileObject->FileName );
for (i=0; i<interface->NumberOfPipes; i++) { PipeInfo = &interface->Pipes[i]; // PUSBD_PIPE_INFORMATION PipeInfo; if ( ourPipeInfo == &deviceExtension->PipeInfo[i] ) { // // found a match // USB2COM_KdPrint( DBGLVL_DEFAULT,("open pipe %d\n", i)); fileObject->FsContext = PipeInfo; ourPipeInfo->fPipeOpened = TRUE; // set flag for opened ntStatus = STATUS_SUCCESS; deviceExtension->OpenPipeCount++; // try to power up device if its not in D0 actStat = USB2COM_SelfSuspendOrActivate( DeviceObject, FALSE ); break; } }