精华内容
下载资源
问答
  • Windows 2000下虚拟串口 WDM 驱动程序的开发 孙筱萌,夏 斌,韩德红,方 晓 (空军雷达学院电子对抗系,武汉 430019 摘 要:针对传统 RS-232串行通信存在的通信距离 端口数量等多个方面的限制, 提出了在 Windows 2000 操作...
  • 开发虚拟串口驱动程序

    千次阅读 2010-03-17 16:29:00
    转自:http://www2.ccw.com.cn/05/0501/d/0501d04_1.asp 虚拟串口就是当本地并没有...本文作者给出了一种在Windows平台上实现虚拟串口的方法,由此实现的“串口”具有真实串口完全相同的系统调用接口。 在很多应用中

       转自:http://www2.ccw.com.cn/05/0501/d/0501d04_1.asp

     

       虚拟串口就是当本地并没有对应的串口硬件设备,而为应用层提供串口设备一样的系统调用接口,以兼容原本使用本地串口的应用软件的“虚”设备。本文作者给出了一种在Windows平台上实现虚拟串口的方法,由此实现的“串口”具有真实串口完全相同的系统调用接口。

    在很多应用中需要用到虚拟串口,如在Modem卡出现之前,已经有了接在计算机串口上的外部Modem,而且各种拔号程序也是通过串口与外部Modem通信的。为了让已有的拔号程序不做修改,像使用外部Modem一样使用内置卡,就需要内置卡的驱动程序虚拟一个串口设备。又如当前工业界使用的一些串口服务器,往往有8个或16个甚至更多的串口,以连接多个串口设备,再通过一个网卡直接连入以太网。与它在同一网络上的计算机就通过以太网与串口服务器上挂接的串口设备通信。为了让计算机中原来使用本地串口的软件兼容,就需要在计算机上提供虚拟串口驱动。

    虚拟串口的设计关键在于,该“串口”实现后必须具有与真实串口完全相同的系统调用接口。要做到这点,从已有的串口设备驱动程序上做修改是最佳捷径。下文就介绍以Windows NT上的串口驱动程序为基础,开发可运行于Windows NT、Windows 2000、Windows XP的各个版本虚拟串口驱动程序。

    串口驱动中使用的几个链表

    由于串口是双工设备,在一个读请求发出来还没有完成之前,同时可以发出写请求,加上在驱动程序层所有I/O请求都要求异步完成,即前一个请求尚没有完成,下一个相同的请求可能又来了。为此,串口驱动程序需要使用多个双向链表数据结构来处理各种IRP(I/O Request Packet,I/O请求包)。当收到一个IRP,先判断是否可立即完成,可以马上处理并返回,如果不允许则将IRP插在相应链表尾,在适当的时候如设备有空闲时处理,这时往往会产生一个硬件中断,激发DPC(Deferred Procedure Call,暂缓过程调用)过程,由DPC处理函数逐个从链表头取出IRP并试着完成它。串口驱动中有以下几个链表和DPC(在serial.h中有定义):

    ReadQueue 和 CompleteReadDpc

    用于保存Read IRP的链表和用于调度的DPC,与DPC对应的处理函数是SerialCompleteRead,它在read.c文件中,该函数的主要任务就是从ReadQueue中提取下一个IRP,并试着完成它。

    WriteQueue 和 CompleteWriteDpc

    用于保存Write IRP的链表和对应的DPC,与DPC对应的函数是SeriaCompleteWrite,它的实现在write.c中,该函数负责从WriteQueue中提取IRP,并试着完成它。

    MaskQueue 和 CommWaitDpc

    这一对链表用于处理Windows串口驱动的一个特性:事件驱动机制。它允许应用程序预设一个事件标志,而后等待与标志对应事件发生。DPC所调用的函数是SerialCompleteWait,它实现在Waitmask.c文件中,该函数也是试着从MaskQueue中提取IRP并完成它。

    PurgeQueue

    该链表与前面几个稍有不同,它没有与之相对应的DPC机制,而是在每次收到Purge请求时从PurgeQueue中逐个提取IRP并试着完成,因某种原因不能完成时则插入链表。相应的函数是purge.c文件中的SerialStartPurge。

    以上机制是串口驱动程序的重要实现方法,在虚拟串口驱动中需要保留,但不同的是,硬件串口驱动中是ISR(中断服务程序)根据收、发或MODEM中断来激发相应的DPC,而在虚拟串口驱动中将因实际情况不同会有不同的激发机制。

    DriverEntry的实现

    DriverEntry是驱动程序的入口函数,相当于应用程序C语言中的main函数,开发一个虚拟串口驱动首先要修改的就是它。它的函数实体在initunlo.c文件中。只是在虚拟串口驱动中由于不与具体的硬件打交道,就不存在硬件资源分析、硬件初始化、判断其工作状态等处理,只需要为虚拟串建立设备对象、符号链接和初始化数据结构。一个典型函数实现大体如下:

    NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

    {

    /*填写DriverObject->MajorFunction[]数组*/

    /*建立设备对象*/

    /*初始化SERIAL_DEVCIE_EXETENSION数据结构*/

    Status = IoCreateDevice(DriverObject, sizeof(SERIAL_DEVICE_EXTENSION), &uniNameString, FILE_DEVICE_SERIAL_PORT, 0,TRUE,&deviceObject);

    //初始化所有链表

    InitializeListHead(&extension->ReadQueue);

    InitializeListHead(…);

    …;

    //初始化所有DPC

    KeInitializeDpc(&extension->CompleteReadDpc,SerailCompleteRead,extension);

    KeInitializeDpc(…);

    /*建立符号链接*/

    SerialSetupExternalNaming(extension);

    return Status;

    }

    SerialRead和SerialCompleteRead的实现

    函数SerailRead和SerialCompleteRead决定了对Read IRP的响应策略,它们都存于read.c中。以串口服务器要用的虚拟串口为例,当串口服务器收到来自外部数据时将通过网络发至计算机,计算机则产生相应的网络中断并进行协议数据处理。网络接收线程缓存新收到的数据并激活CompleteReadDpc,从而SerialCompleteReadIrp得到调用,它再调用CompleteReadIrp对每个IRP进行处理。它们的实现大体如下:

    NTSTATUS SerialRead(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

    {

    /*此处略去变量声明和初始化*/

    /*提取IRP中相关的数据*/

    stack = IoGetCurrentIrpStackLocation(Irp);

    ReadLen = stack->Parameters.Read.Length;

    /*先看本地缓冲有数据否?有的话先读取*/

    if(Extension->InCounter > 0 )

    { //注意这里要加锁,以防数据访问冲突

    KeAcquireSpinLock(&Extension->

    ReadBufferLock,&lIrql);

    FirstRead = (ReadLen>Extension->

    InCounter)? Extension->InCounter: ReadLen;

    RtlCopyMemory(Irp->AssociatedIrp.

    SystemBuffer,Extension->pInBuffer,FirstRead);

    Extension->InCounter -= FirstRead;

    ReadLen -= FirstRead;

    KeReleaseSpinLock(&Extension->

    ReadBufferLock,lIrql);//释放锁

    }

    /*是否已读到足够数据?是的话则完成该IRP*/

    if( 0 == ReadLen)

    {

    status=STATUS_SUCCESS;

    Irp->IoStatus.Status = status;

    Irp->IoStatus.Information = FirstRead;

    IoCompleteRequest(Irp,0);

    return status;

    }

    /*没有则将IRP插入队列中,通过网络向串口服务器发出读数据请求*/

    IoMarkIrpPending(Irp);

    InsertWaitList(Extension->ReadQueue,Irp);

    status = TdiSendAsync(Extension->ComChannel,pAckPacket,PacketLen(pAckPacket),(PVOID)ReadAckComplete,Irp);

    /*返回PENDING,表示该IRP尚没有完成*/

    return STATUS_PENDING;

    }

    Void CompleteReadIrp(IN PSERIAL_DEVICE_EXTENSION extension,IN PIRP Irp,IN PUCHAR pInData,IN ULONG Length )

    {

    /*此处略去变量声明和初始化*/

    /*读取新数据*/

    ReadLen = (ReadLen > Length)? Length : ReadLen;

    if(ReadLen != 0)

    {

    RtlCopyMemory(pReadAsync->

    pReadBuffer,pInData,ReadLen);

    pReadAsync->pReadBuffer += ReadLen;

    pReadAsync->ReadAlready += ReadLen;

    extension->PerfStats.ReceivedCount +=

    ReadLen;

    }

    else

    {

    /*因为串口服务器端只有在已经有了相应的数据或超过时间(此时,Length=0)才会发来应答并激活本DPC过程,所以此时已经超时,为了便于结束本IRP,这里有意改变TotalNeedRead,造成接收完毕的假象*/

    pReadAsync->TotalNeedRead =

    pReadAsync->ReadAlready;

    }

    if(pReadAsync->TotalNeedRead == pReadAsync->ReadAlready)

    {

    /*该IRP是否已经接收完毕,是的话则结束该

    IRP*/

    EndReadIrp(Irp);

    /*从ReadQueue中取下一个IRP*/

    }

    /*本IRP没有完成也没有超时,则继续等待本DPC下次被激活,注意此时要判断IRP是否被要求取消*/

    }

    SerialWrite和SerailCompleteWrite的实现

    SerialWrite和SerailCompleteWrite决定了Write IRP的实现。在SerialWrite中调用了网络发送函数TdiSendAsync,当该发送完成后将激活CompleteWriteDpc,调度SerialCompleteWrite函数,而它主要就是取出当前的WriteIRP,设置已经发送的数据数量,调用CompleteWriteIrp做该IRP的进一步处理。它们大体如下:

    NTSTATUS SerialWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

    {

    /*此处略去变量声明和初始化*/

    /*从IRP中提取有关数据*/

    stack=IoGetCurrentIrpStackLocation(Irp);

    SendLen = stack->Parameters.Write.Length;

    /*为网络发送和异步操作分配缓冲,在CompleteWrite中全部数据发送完后释放*/

    pWriteAsync = ExAllocatePool(NonPagedPool,

    SendLen+PACKET_HEADER_LEN+sizeof(WRITE_ASYNC));

    if(pWriteAsync == NULL)

    {

    //错误处理

    }

    //保存异步数据

    //设置网络发送数据包

    BuildDataPacket(pPacket,WRITE,(USHORT)SendLen,pWriteAsync->pWriteBuffer);

    /*先将IRP暂时阻塞并插入队列,在CompleteWrite中完成*/

    IoMarkIrpPending(Irp);

    InsertWaitList(extension->WriteQueue, Irp);

    /*将写请求和相关数据通过网络发向串口服务器,由它负责将数据传到具体串口设备*/

    status = TdiSendAsync(Extension->ComChannel,pPacket,PacketLen(pPacket),(PVOID)CompleteWriteIrp,Irp);

    //统计数据累加

    Extension->PerfStats.TransmittedCount += SendLen;

    return STATUS_PENDING;

    }

     

    NTSTATUS CompleteWriteIrp(IN PDEVICE_OBJECT deviceobject,IN PIRP pIrp,IN PVOID context)

    {

    /*此处略去变量声明和初始化*/

    SendLen=pWriteAsync->TotalNeedWrite - pWriteAsync->WroteAlready;

    if(SendLen == 0)//全部数据发送完毕

    {

    EndWaitWriteIrp(pWriteIrp,STATUS_SUCCESS,

    pWriteAsync->WroteAlready,pWriteAsync);

    //从WriteQueue中取下一个IRP;

    }

    else //发送剩余数据

    {

    if(pWriteIrp->Cancel)

      {

    //IRP被要求取消,完成WriteIrp

    EndWaitWriteIrp(pWriteIrp,STATUS_CANCELLED,

    pWriteAsync->WroteAlready,pWriteAsync);

    return STATUS_CANCELED;

    }

    else

    {

    //再次设置网络数据包并发送

    BuildDataPacket(…);

    status = TdiSendAsync(…);

    //统计数据累加

    Extension->PerfStats.TransmittedCount +=

    SendLen;

    return STATUS_MORE_PROCESSING_REQUIRED;

    }

    }

    }

    其他几个接口函数的实现

    除Read/Write外,SerialUnload、SerialCreateOpen、 SerialClose、SerialCleanup、SerailFlush等调用接口是硬件相关性比较弱的接口函数,基本不要修改,直接删除原来操作硬件的部分即可。复杂一点就是SerialIoControl,该接口函数包含有大量设置、读取串口硬件状态的处理,可建立一个本地数据结构随时保存虚拟串口的当前硬件状态。同时为了保证串口服务器端的真实串口状态和上层软件要求的一致,需要将所有设置请求通过网络发送到服务器端,由它负责改变真实硬件的状态。

    展开全文
  • Windows驱动开发——虚拟串口设备

    千次阅读 2013-10-14 19:45:24
    DDK对串口驱动提供了专门接口。只要编写的驱动满足这些接口,并按照串口标准的命名方法,不管是真实的串口设备,还是虚拟设备,Windows操作系统都会认为 这个设备是一个标准的串口设备。用标准的串口调试工具都可以...

    文章转自:http://blog.csdn.net/chenyujing1234/article/details/7896364

    1、DDK串口开发框架

    DDK对串口驱动提供了专门接口。只要编写的驱动满足这些接口,并按照串口标准的命名方法,不管是真实的串口设备,还是虚拟设备,Windows操作系统都会认为

    这个设备是一个标准的串口设备。用标准的串口调试工具都可以与这个设备进行通信

    1、1 串口驱动的入口函数

    本章的实例程序是在HelloWDM驱动的基础上修改而来,入口函数依然是DriverEntry,在DriverEntry函数中指定各种IRP的派遣函数,以及AddDevice 例程、卸载例程等。

    /************************************************************************
    * 函数名称:DriverEntry
    * 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
    * 参数列表:
          pDriverObject:从I/O管理器中传进来的驱动对象
          pRegistryPath:驱动程序在注册表的中的路径
    * 返回 值:返回初始化驱动状态
    *************************************************************************/
    #pragma INITCODE 
    extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
    								IN PUNICODE_STRING pRegistryPath)
    {
    	KdPrint(("Enter DriverEntry\n"));
    
    	pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
    	pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
    	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloWDMDispatchControlp;
    	pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloWDMCreate;
    	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloWDMClose;
    	pDriverObject->MajorFunction[IRP_MJ_READ] = HelloWDMRead;
    	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMWrite;
    	pDriverObject->DriverUnload = HelloWDMUnload;
    
    	KdPrint(("Leave DriverEntry\n"));
    	return STATUS_SUCCESS;
    }

    其中在AddDevice例程中,需要创建设备对象,这些都是和以前的HelloWDM驱动程序类似。在创建完设备对象后,需要将设备对象指定一个符号链接,该符号链接必须是

    COM开头,并接一下数字,如本例就采用了COM7。因为COM1和COM2在有些计算机中有时会被占用,因此,当该设备对象在指定符号链接时,应该避免采用这些名称。

    /************************************************************************
    * 函数名称:HelloWDMAddDevice
    * 功能描述:添加新设备
    * 参数列表:
          DriverObject:从I/O管理器中传进来的驱动对象
          PhysicalDeviceObject:从I/O管理器中传进来的物理设备对象
    * 返回 值:返回添加新设备状态
    *************************************************************************/
    #pragma PAGEDCODE
    NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                               IN PDEVICE_OBJECT PhysicalDeviceObject)
    { 
    	PAGED_CODE();
    	KdPrint(("Enter HelloWDMAddDevice\n"));
    
    	NTSTATUS status;
    	PDEVICE_OBJECT fdo;
    	UNICODE_STRING devName;
    	RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");
    	status = IoCreateDevice(
    		DriverObject,
    		sizeof(DEVICE_EXTENSION),
    		&(UNICODE_STRING)devName,
    		FILE_DEVICE_UNKNOWN,
    		0,
    		FALSE,
    		&fdo);
    	if( !NT_SUCCESS(status))
    		return status;
    	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    	pdx->fdo = fdo;
    	pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
    	UNICODE_STRING symLinkName;
    	RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\COM7");
    
    	pdx->ustrDeviceName = devName;
    	pdx->ustrSymLinkName = symLinkName;
    	status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);
    
    	if( !NT_SUCCESS(status))
    	{
    		IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
    		status = IoCreateSymbolicLink(&symLinkName,&devName);
    		if( !NT_SUCCESS(status))
    		{
    			return status;
    		}
    	}
    	// 设置为缓冲区设备
    	fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
    	fdo->Flags &= ~DO_DEVICE_INITIALIZING;
    
    	KdPrint(("Leave HelloWDMAddDevice\n"));
    	return STATUS_SUCCESS;
    }
    

    在创建完符号链接后,还不能保证应用程序能找出这个虚拟的串口设备,还需要进一步修改注册表。具体位置是HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM,可以在这里加入新项目。本例的项目名是MyWDMDevice,类型为REG_SZ,内容是COM7。

    在上述步骤后,即在AddDevice例程中创建COM7的符号链接,并且在注册表进行相应设置,系统会认为有这个串口驱动,用任何一个串口调试软件,都可以枚举到

    该串口。

    1、2  应用程序与串口驱动的通信

    其实对于一个真实的串口驱动,或者这个介绍的虚拟串口驱动,都需要遵循一组接口。这组接口由微软事先定义好了,只要符合这组接口,

    windows就会认为这是一个串口设备。这里所指的接口就是应用程序发的IO控制码和读写命令,因此对于串口驱动只要对这些IRP的派遣函数编写适当,就能实现一个串口驱动。

    首先用IRPTrace看一下,需要对哪些IRP进行处理,笔者加载本章已经介绍的虚拟串口驱动,并用IRPTrace 拦截其IRP处理信息,在打开串口工具后,会发现IRPTrace立刻跟踪到若干个IO控制码。

    下面依次解释这些IO控制码,理解这些IO控制码,并处理好这些控制码,是编写串口驱动的核心。关于这些IO控制码在ntddser.h文件中,都有相应的定义,并且还有

    相应的数据结构定义。

    (1)IOCTL_SERIAL_SET_QUEUE_SIZE

    这个控制码是应用程序向驱动请求设置串口驱动内部的缓冲区大小,它是向驱动传递SEARIAL_QUEUE_SIZE 数据结构来进行设置的,对于虚拟串口驱动来说,这是不需要

    关心的。用IRPTrace可以看出,串口调试工具会向驱动发送的请求是0x400大小的缓冲区大小。

    (2)IOCTL_SERIAL_GET_BAUD_RATE

    串口调试工具会接着向驱动发送IOCTL_SERIAL_GET_BAUD_RATE命令,这主要是询问驱动这个设备的波特率。驱动应该回应应用程序SEARIAL_BAUD_RATE数据结构,来通知波特率的数值。

    (3)IOCTL_SERIAL_GET_LINE_CONTROL

    串口调试工具接着向驱动发送IOTCL_SERIAL_GET_LINE_CONTROL命令,这主要是为了返回串口的行控制信息,行控制信息用SERIAL_LINE_CONTROL数据结构表示。

    1. typedef struct _SERIAL_LINE_CONTROL {  
    2.     UCHAR StopBits;  
    3.     UCHAR Parity;  
    4.     UCHAR WordLength;  
    5.     } SERIAL_LINE_CONTROL,*PSERIAL_LINE_CONTROL;  

    其中StopBits是停止位,可以是STOP_BIT_1、STOP_BITS_1_5、STOP_BITS_2等取值。

    Parity代表校验位,可以是NO_PARITY、ODD——PARITY、EVEN_PARITY、MARK——PARITY、SPACE_PARITY。WorkLength是数据位,可以是5、6、7、8。

    1. case IOCTL_SERIAL_GET_LINE_CONTROL:   
    2. {  
    3.     *((PSERIAL_LINE_CONTROL)(Irp->AssociatedIrp.SystemBuffer)) = pdx->Lc;  
    4.   
    5.           Irp->IoStatus.Information = sizeof(SERIAL_LINE_CONTROL);  
    6.     break;  
    7. }  

    (4)IOCTL_SERIAL_GET_CHARS

    串口调试工具会接着向驱动发送IOCTL_SERIAL_GET_CHARS命令,这个命令是应用程序向驱动请求特殊字符,用来与控制信号握手,用数据结构SERIAL_CHARS表示。

    1. typedef struct _SERIAL_CHARS {  
    2.     UCHAR EofChar;  
    3.     UCHAR ErrorChar;  
    4.     UCHAR BreakChar;  
    5.     UCHAR EventChar;  
    6.     UCHAR XonChar;  
    7.     UCHAR XoffChar;  
    8.     } SERIAL_CHARS,*PSERIAL_CHARS;  


    其中EofChar代表是否是传送结束、ErrorChar代码是否传送中有错误、BreadChar代码是否传送有停止等。

    (5)IOCTL_SERIAL_GET_HANDFLOW

    串口调试工具会接着向驱动发送IOCTL_SRIAL_GET_HANDFLOW命令,这个命令是负责向驱动程序获得串口驱动的握手信号,握手信号用SERIAL_HANDFLOW数据

    结构表示:

    1. typedef struct _SERIAL_HANDFLOW {  
    2.     ULONG ControlHandShake;  
    3.     ULONG FlowReplace;  
    4.     LONG XonLimit;  
    5.     LONG XoffLimit;  
    6.     } SERIAL_HANDFLOW,*PSERIAL_HANDFLOW;  


    (6)IOCTL_SERIAL_SET_WAIT_MASK

    串口工具会接着向驱动发送IOCTL_SERIAL_SET_WAIT_MASK命令,这个命令主要是设置串口驱动的某些事件发生时,需要向应用程序通知,这些事件包括以下几种事件:

    1. #define SERIAL_EV_RXCHAR           0x0001  // Any Character received  
    2. #define SERIAL_EV_RXFLAG           0x0002  // Received certain character  
    3. #define SERIAL_EV_TXEMPTY          0x0004  // Transmitt Queue Empty  
    4. #define SERIAL_EV_CTS              0x0008  // CTS changed state  
    5. #define SERIAL_EV_DSR              0x0010  // DSR changed state  
    6. #define SERIAL_EV_RLSD             0x0020  // RLSD changed state  
    7. #define SERIAL_EV_BREAK            0x0040  // BREAK received  
    8. #define SERIAL_EV_ERR              0x0080  // Line status error occurred  
    9. #define SERIAL_EV_RING             0x0100  // Ring signal detected  
    10. #define SERIAL_EV_PERR             0x0200  // Printer error occured  
    11. #define SERIAL_EV_RX80FULL         0x0400  // Receive buffer is 80 percent full  
    12. #define SERIAL_EV_EVENT1           0x0800  // Provider specific event 1  
    13. #define SERIAL_EV_EVENT2           0x1000  // Provider specific event 2  
    1. case IOCTL_SERIAL_SET_WAIT_MASK:  
    2.         {  
    3.             PIRP            pOldWaitIrp;  
    4.             PDRIVER_CANCEL  pOldCancelRoutine;  
    5.   
    6.             pdx->EventMask = *(PULONG)Irp->AssociatedIrp.SystemBuffer;  
    7.   
    8.             KeAcquireSpinLock(&pdx->IoctlSpinLock, &OldIrql);  
    9.   
    10.             pOldWaitIrp = pdx->pWaitIrp;  
    11.             if (pOldWaitIrp != NULL)  
    12.             {  
    13.                 pOldCancelRoutine = IoSetCancelRoutine(pOldWaitIrp, NULL);  
    14.   
    15.                 //对以前没有进行完成例程的等待irp,进行完成  
    16.                 if (pOldCancelRoutine != NULL)  
    17.                 {  
    18.                     pOldWaitIrp->IoStatus.Information = sizeof(ULONG);  
    19.                     *(PULONG)pOldWaitIrp->AssociatedIrp.SystemBuffer = 0;  
    20.   
    21.                     pOldWaitIrp->IoStatus.Status = STATUS_SUCCESS;  
    22.   
    23.                     pdx->pWaitIrp = NULL;  
    24.                 }  
    25.                 else  
    26.                 {  
    27.                     pOldWaitIrp = NULL;  
    28.                 }  
    29.             }  
    30.   
    31.             KeReleaseSpinLock(&pdx->IoctlSpinLock, OldIrql);  
    32.   
    33.             if (pOldWaitIrp != NULL)  
    34.             {  
    35.                 IoCompleteRequest(pOldWaitIrp, IO_NO_INCREMENT);  
    36.             }  
    37.   
    38.             break;  
    39.         }  

    当设置新的阻塞事件时,哪果之前有等待的IRP,那么先进行完成

    (7)IOCTL_SERIAL_WAIT_ON_MASK

    这个IO控制码是最重要的一个,当串口调试工具通过前面几个IO控制码初始华好后,就会发送这个请求。

    在驱动程序中,应该阻塞在那里,即返回PENDING状态,且通过IoSetCancelRoutine设置取消例程,而不是完成这个IRP。

    当IOCTL_SERIAL_SET_WAIT_MASK设置的事件中的一项发生时,阻塞状态改为完成,并通知应用程序是哪种事件发生了。

    1. case IOCTL_SERIAL_WAIT_ON_MASK:  
    2.         {  
    3.             PDRIVER_CANCEL  pOldCancelRoutine;  
    4.   
    5.             KeAcquireSpinLock(&pdx->IoctlSpinLock, &OldIrql);  
    6.   
    7.             //等待irp一定被清除,且eventMask一定不为0  
    8.             if ((pdx->pWaitIrp != NULL) || (pdx->EventMask == 0))  
    9.                 ntStatus = STATUS_INVALID_PARAMETER;  
    10.             else if ((pdx->EventMask & pdx->HistoryEvents) != 0)  
    11.             {  
    12.                 // Some events happened  
    13.                 Irp->IoStatus.Information = sizeof(ULONG);  
    14.                 *(PULONG)Irp->AssociatedIrp.SystemBuffer = pdx->EventMask & pdx->HistoryEvents;  
    15.                 pdx->HistoryEvents = 0;  
    16.                 ntStatus = STATUS_SUCCESS;  
    17.             }else  
    18.             {  
    19.                 pdx->pWaitIrp = Irp;  
    20.   
    21.                 ntStatus = STATUS_PENDING;  
    22.   
    23.                 IoSetCancelRoutine(Irp, DriverCancelWaitIrp);  
    24.   
    25.                 if (Irp->Cancel)  
    26.                 {  
    27.                     pOldCancelRoutine = IoSetCancelRoutine(Irp, NULL);  
    28.   
    29.                     if (pOldCancelRoutine != NULL)  
    30.                     {  
    31.                         ntStatus = STATUS_CANCELLED;  
    32.   
    33.                         pdx->pWaitIrp = NULL;  
    34.                     }  
    35.                     else  
    36.                     {  
    37.                         IoMarkIrpPending(Irp);  
    38.                     }  
    39.                 }  
    40.                 else  
    41.                 {  
    42.                     IoMarkIrpPending(Irp);  
    43.                 }  
    44.             }  
    45.   
    46.             KeReleaseSpinLock(&pdx->IoctlSpinLock, OldIrql);  
    47.             break;  
    48.   
    49.         }  

     

    1. VOID DriverCancelWaitIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)  
    2. {  
    3.     KdPrint(("DriverCancelWaitIrp\n"));  
    4.     PDEVICE_EXTENSION pExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;  
    5.     KIRQL                   OldIrql;  
    6.   
    7.     IoReleaseCancelSpinLock(Irp->CancelIrql);  
    8.   
    9.     KeAcquireSpinLock(&pExtension->IoctlSpinLock, &OldIrql);  
    10.   
    11.     pExtension->pWaitIrp = NULL;          
    12.   
    13.     KeReleaseSpinLock(&pExtension->IoctlSpinLock, OldIrql);  
    14.   
    15.     Irp->IoStatus.Status = STATUS_CANCELLED;  
    16.     IoCompleteRequest(Irp, IO_NO_INCREMENT);  
    17. }  


    关于删除例程的知识可以参考<<驱动程序的取消IRP >>

    1、3  写的实现

    串口驱动除了需要完成处理IO控制码外,还需要对读写IRP进行处理。一般情况下,作为应用程序的串口调试工具会开启多个线程,其中主线程负责与串口驱动初始化的IO控制码通信。

    另外一个很重要的线程就是发送IOCTL_SERIAL_WAIT_ON_MASK请求,对于没有数据传输的情况下,这个IO控制码请求会PENDING在那里,即阻塞。当有传送的请求时,相应的事件被触发,刚才因为IOCTL_SERAIL_WAIT_ON_MASK的IRP被阻塞的线程得以继续运行,

    如果应用程序得知该事件是被写入了一个字符,会去发出一个读请求 ,对于驱动则是读的IRP。如果循环过程,从而实现了一个虚拟摄像头回写的例子。

    在对于写IRP的派遣函数中,主要是将写的数据存储在设备扩展中,以便以后读的时候将这些内容返回到应用程序。

    另外一个很重要的内容,就是阻塞的IO控制苏醒过来。在本例中调用DriverCheckEvent函数,该函数将阻塞的IRP完成,使应用程序的线程得以继续进行。并且这个线程还知道了SERIAL_EV_RXCHAR和SERIAL_EV_RX80FULL事件的到来,从而发起一个读请求,传送到驱动中就是读IRP。

    1. NTSTATUS HelloWDMWrite(IN PDEVICE_OBJECT fdo,  
    2.                         IN PIRP Irp)  
    3. {  
    4.     KdPrint(("HelloWDMWrite\n"));  
    5.       
    6.     NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success  
    7.   
    8.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
    9.     // 获得当前IO堆栈  
    10.     PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );  
    11.     // 获取当前IO堆栈的操作字节数  
    12.     ULONG DataLen = irpSp->Parameters.Write.Length;  
    13.     // 从IRP的缓冲区中得到数据  
    14.     PUCHAR pData = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;  
    15.     KIRQL OldIrql;  
    16.     PIRP            pOldReadIrp = NULL;  
    17.     PDRIVER_CANCEL  pOldCancelRoutine;  
    18.     // 设置IRP的操作字节数  
    19.     Irp->IoStatus.Information = 0;  
    20.     ntStatus = STATUS_SUCCESS;  
    21.   
    22.     if (DataLen == 0)  
    23.     {  
    24.         ntStatus = STATUS_SUCCESS;  
    25.     }else if (DataLen>COMBUFLEN)  
    26.     {  
    27.         ntStatus = STATUS_INVALID_PARAMETER;  
    28.     }  
    29.     else  
    30.     {  
    31.         KdPrint(("Write\n"));  
    32.         // 获取自旋锁  
    33.         KeAcquireSpinLock(&pdx->WriteSpinLock, &OldIrql);  
    34.         // 复制内存块  
    35.         RtlCopyMemory(pdx->Buffer,pData,DataLen);  
    36.   
    37.         pdx->uReadWrite = DataLen;  
    38.   
    39.         if (pdx->pReadIrp != NULL) // drop it out  
    40.         {  
    41.             // 记录IRP  
    42.             pOldReadIrp = pdx->pReadIrp;  
    43.             // 设置取消函数  
    44.             pOldCancelRoutine = IoSetCancelRoutine(pOldReadIrp, NULL);  
    45.   
    46.             if (pOldCancelRoutine != NULL)  
    47.             {  
    48.                 pOldReadIrp->IoStatus.Information = 0;  
    49.   
    50.                 pOldReadIrp->IoStatus.Status = STATUS_SUCCESS;  
    51.   
    52.                 pdx->pReadIrp = NULL;  
    53.             }  
    54.             else  
    55.             {  
    56.                 pOldReadIrp = NULL;  
    57.             }  
    58.   
    59.         }  
    60.         // 检查事件  
    61.         DriverCheckEvent(pdx, SERIAL_EV_RXCHAR | SERIAL_EV_RX80FULL);  
    62.   
    63. //      DriverCheckEvent(pdx, SERIAL_EV_TXEMPTY);  
    64.         // 释放自旋锁  
    65.         KeReleaseSpinLock(&pdx->WriteSpinLock, OldIrql);  
    66.   
    67.         if (pOldReadIrp != NULL)  
    68.             IoCompleteRequest(pOldReadIrp, IO_NO_INCREMENT);  
    69.     }  
    70.   
    71.     Irp->IoStatus.Status = ntStatus;  
    72.     Irp->IoStatus.Information = DataLen;  
    73.     IoCompleteRequest( Irp, IO_NO_INCREMENT );  
    74.   
    75.     return ntStatus;  
    76. }  


     

    1. VOID DriverCheckEvent(IN PDEVICE_EXTENSION pExtension, IN ULONG events)  
    2. {  
    3.     KdPrint(("DriverCheckEvent\n"));  
    4.     PIRP            pOldWaitIrp = NULL;  
    5.     PDRIVER_CANCEL  pOldCancelRoutine;  
    6.     KIRQL           OldIrql;  
    7.   
    8.     KeAcquireSpinLock(&pExtension->IoctlSpinLock, &OldIrql);  
    9.   
    10.     pExtension->HistoryEvents |= events;  
    11.   
    12.     events &= pExtension->EventMask;  
    13.   
    14.     //相当于设置触发事件  
    15.     if ((pExtension->pWaitIrp != NULL) && (events != 0))  
    16.     {  
    17.         pOldWaitIrp = pExtension->pWaitIrp;  
    18.   
    19.         pOldCancelRoutine = IoSetCancelRoutine(pOldWaitIrp, NULL);  
    20.   
    21.         //是否已经被cancel掉?  
    22.         if (pOldCancelRoutine != NULL)  
    23.         {  
    24.             // Nein, also Request beenden  
    25.             pOldWaitIrp->IoStatus.Information = sizeof(ULONG);  
    26.             *(PULONG)pOldWaitIrp->AssociatedIrp.SystemBuffer = events;  
    27.   
    28.             pOldWaitIrp->IoStatus.Status = STATUS_SUCCESS;  
    29.   
    30.             pExtension->pWaitIrp      = NULL;  
    31.             pExtension->HistoryEvents = 0;  
    32.         }  
    33.         else  
    34.         {  
    35.             //如果cancel掉,就不用IoCompleteRequest了  
    36.             pOldWaitIrp = NULL;  
    37.         }  
    38.     }  
    39.   
    40.     KeReleaseSpinLock(&pExtension->IoctlSpinLock, OldIrql);  
    41.   
    42.     if (pOldWaitIrp != NULL)  
    43.     {  
    44.         KdPrint(("complete the wait irp\n"));  
    45.         IoCompleteRequest(pOldWaitIrp, IO_NO_INCREMENT);  
    46.     }  
    47. }  


    1、4  读的实现

    对于虚拟串口的读工作,就变得相对简单。因为写IRP会负责通知让阻塞的线程继续运行,并且通知是何种事件的来临。串口调试软件得知SERAIL_EV_RXCHAR这个事件

    ,因此发起了读事件。在驱动中,就是进入读IRP的派遣函数。

    在该派遣函数中,负责将存储在设备扩展中的数据通过IRP传送到应用程序。同时,还需要做一些同步处理。

    1. NTSTATUS HelloWDMRead(IN PDEVICE_OBJECT fdo,  
    2.                         IN PIRP Irp)  
    3. {  
    4.     KdPrint(("HelloWDMRead\n"));  
    5.   
    6.     NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success  
    7.   
    8.     PDEVICE_EXTENSION pExtension = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
    9.   
    10.     PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );  
    11.   
    12.     ULONG BufLen = irpSp->Parameters.Read.Length;  
    13.     PCHAR pBuf = (PCHAR)Irp->AssociatedIrp.SystemBuffer;  
    14.   
    15.     KIRQL OldIrql;  
    16.   
    17.     PDRIVER_CANCEL pOldCancelRoutine;  
    18.   
    19.     Irp->IoStatus.Information = 0;  
    20.       
    21.     DbgPrint("DeviceObject:%08X Read\n",fdo);  
    22.   
    23.     if (BufLen == 0)  
    24.     {  
    25.         ntStatus = STATUS_SUCCESS;  
    26.     }  
    27.     else  
    28.     {  
    29.         KeAcquireSpinLock(&pExtension->WriteSpinLock, &OldIrql);  
    30.         // 内存复制  
    31.         RtlCopyMemory(pBuf,pExtension->Buffer,BufLen);  
    32.   
    33.         Irp->IoStatus.Information = BufLen;  
    34.   
    35.         if (BufLen==0 && pExtension->pReadIrp==NULL) // nothing, store  
    36.         {  
    37.             // 保存IRP  
    38.             pExtension->pReadIrp = Irp;  
    39.             Irp->IoStatus.Status = ntStatus = STATUS_PENDING;  
    40.             // 设置取消函数  
    41.             IoSetCancelRoutine(Irp, DriverCancelCurrentReadIrp);  
    42.   
    43.             // 重新设置取消函数  
    44.             if (Irp->Cancel)  
    45.             {  
    46.                 pOldCancelRoutine = IoSetCancelRoutine(Irp, NULL);  
    47.   
    48.                 if (pOldCancelRoutine != NULL)  
    49.                 {  
    50.                     // Nein, also IRP hier abbrechen  
    51.                     Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED;  
    52.   
    53.                     pExtension->pReadIrp = NULL;  
    54.                 }  
    55.                 else  
    56.                 {  
    57.                     // 标记IRP挂起   Ja, Cancel-Routine wird Request beenden  
    58.                     IoMarkIrpPending(Irp);  
    59.                 }  
    60.             }  
    61.             else  
    62.             {  
    63.                     IoMarkIrpPending(Irp);  
    64.             }  
    65.         }  
    66.   
    67.         KeReleaseSpinLock(&pExtension->WriteSpinLock, OldIrql);  
    68.       
    69.     }  
    70.   
    71.     Irp->IoStatus.Status = ntStatus;  
    72.     if (ntStatus != STATUS_PENDING)  
    73.         IoCompleteRequest( Irp, IO_NO_INCREMENT );  
    74.   
    75.     return ntStatus;  
    76. }
    展开全文
  • 第19章,虚拟串口驱动,求讲解。有意的加我qq:545039712.我出钱。 需要讲解的代码如下: NTSTATUS HelloWDMRead(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { KdPrint(("HelloWDMRead\n")); NTSTATUS ntStatus...
  • >>中,讲到在wince 下开发虚拟串口驱动的方法,现在介绍在windows XP下开发虚拟串口的方法。   可以开发一个虚拟串口,将读写请求传递给USB驱动,这样就可以利用现成的串口调试工具向USB设备读取了。 1、DDK串
    
    

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 

    欢迎大家拍砖

     

     在我的一篇文章<<winCE中实现虚拟串口的方法 >>中,讲到在wince 下开发虚拟串口驱动的方法,现在介绍在windows XP下开发虚拟串口的方法。

     

    可以开发一个虚拟串口,将读写请求传递给USB驱动,这样就可以利用现成的串口调试工具向USB设备读取了。

    1、DDK串口开发框架

    DDK对串口驱动提供了专门接口。只要编写的驱动满足这些接口,并按照串口标准的命名方法,不管是真实的串口设备,还是虚拟设备,Windows操作系统都会认为

    这个设备是一个标准的串口设备。用标准的串口调试工具都可以与这个设备进行通信

    1、1 串口驱动的入口函数

    本章的实例程序是在HelloWDM驱动的基础上修改而来,入口函数依然是DriverEntry,在DriverEntry函数中指定各种IRP的派遣函数,以及AddDevice 例程、卸载例程等。

    /************************************************************************
    * 函数名称:DriverEntry
    * 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
    * 参数列表:
          pDriverObject:从I/O管理器中传进来的驱动对象
          pRegistryPath:驱动程序在注册表的中的路径
    * 返回 值:返回初始化驱动状态
    *************************************************************************/
    #pragma INITCODE 
    extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
    								IN PUNICODE_STRING pRegistryPath)
    {
    	KdPrint(("Enter DriverEntry\n"));
    
    	pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
    	pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
    	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloWDMDispatchControlp;
    	pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloWDMCreate;
    	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloWDMClose;
    	pDriverObject->MajorFunction[IRP_MJ_READ] = HelloWDMRead;
    	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMWrite;
    	pDriverObject->DriverUnload = HelloWDMUnload;
    
    	KdPrint(("Leave DriverEntry\n"));
    	return STATUS_SUCCESS;
    }


    其中在AddDevice例程中,需要创建设备对象,这些都是和以前的HelloWDM驱动程序类似。在创建完设备对象后,需要将设备对象指定一个符号链接,该符号链接必须是

    COM开头,并接一下数字,如本例就采用了COM7。因为COM1和COM2在有些计算机中有时会被占用,因此,当该设备对象在指定符号链接时,应该避免采用这些名称。

    /************************************************************************
    * 函数名称:HelloWDMAddDevice
    * 功能描述:添加新设备
    * 参数列表:
          DriverObject:从I/O管理器中传进来的驱动对象
          PhysicalDeviceObject:从I/O管理器中传进来的物理设备对象
    * 返回 值:返回添加新设备状态
    *************************************************************************/
    #pragma PAGEDCODE
    NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                               IN PDEVICE_OBJECT PhysicalDeviceObject)
    { 
    	PAGED_CODE();
    	KdPrint(("Enter HelloWDMAddDevice\n"));
    
    	NTSTATUS status;
    	PDEVICE_OBJECT fdo;
    	UNICODE_STRING devName;
    	RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");
    	status = IoCreateDevice(
    		DriverObject,
    		sizeof(DEVICE_EXTENSION),
    		&(UNICODE_STRING)devName,
    		FILE_DEVICE_UNKNOWN,
    		0,
    		FALSE,
    		&fdo);
    	if( !NT_SUCCESS(status))
    		return status;
    	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    	pdx->fdo = fdo;
    	pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
    	UNICODE_STRING symLinkName;
    	RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\COM7");
    
    	pdx->ustrDeviceName = devName;
    	pdx->ustrSymLinkName = symLinkName;
    	status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);
    
    	if( !NT_SUCCESS(status))
    	{
    		IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
    		status = IoCreateSymbolicLink(&symLinkName,&devName);
    		if( !NT_SUCCESS(status))
    		{
    			return status;
    		}
    	}
    	// 设置为缓冲区设备
    	fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
    	fdo->Flags &= ~DO_DEVICE_INITIALIZING;
    
    	KdPrint(("Leave HelloWDMAddDevice\n"));
    	return STATUS_SUCCESS;
    }
    


    在创建完符号链接后,还不能保证应用程序能找出这个虚拟的串口设备,还需要进一步修改注册表。具体位置是HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM,可以在这里加入新项目。本例的项目名是MyWDMDevice,类型为REG_SZ,内容是COM7。

    在上述步骤后,即在AddDevice例程中创建COM7的符号链接,并且在注册表进行相应设置,系统会认为有这个串口驱动,用任何一个串口调试软件,都可以枚举到

    该串口。

     

    1、2  应用程序与串口驱动的通信

    其实对于一个真实的串口驱动,或者这个介绍的虚拟串口驱动,都需要遵循一组接口。这组接口由微软事先定义好了,只要符合这组接口,

    windows就会认为这是一个串口设备。这里所指的接口就是应用程序发的IO控制码和读写命令,因此对于串口驱动只要对这些IRP的派遣函数编写适当,就能实现一个串口驱动。

    首先用IRPTrace看一下,需要对哪些IRP进行处理,笔者加载本章已经介绍的虚拟串口驱动,并用IRPTrace 拦截其IRP处理信息,在打开串口工具后,会发现IRPTrace立刻跟踪到若干个IO控制码,如下所示:

     

     

    下面依次解释这些IO控制码,理解这些IO控制码,并处理好这些控制码,是编写串口驱动的核心。关于这些IO控制码在ntddser.h文件中,都有相应的定义,并且还有

    相应的数据结构定义。

    (1)IOCTL_SERIAL_SET_QUEUE_SIZE

    这个控制码是应用程序向驱动请求设置串口驱动内部的缓冲区大小,它是向驱动传递SEARIAL_QUEUE_SIZE 数据结构来进行设置的,对于虚拟串口驱动来说,这是不需要

    关心的。用IRPTrace可以看出,串口调试工具会向驱动发送的请求是0x400大小的缓冲区大小。

    (2)IOCTL_SERIAL_GET_BAUD_RATE

    串口调试工具会接着向驱动发送IOCTL_SERIAL_GET_BAUD_RATE命令,这主要是询问驱动这个设备的波特率。驱动应该回应应用程序SEARIAL_BAUD_RATE数据结构,来通知波特率的数值。

    (3)IOCTL_SERIAL_GET_LINE_CONTROL

    串口调试工具接着向驱动发送IOTCL_SERIAL_GET_LINE_CONTROL命令,这主要是为了返回串口的行控制信息,行控制信息用SERIAL_LINE_CONTROL数据结构表示。

    typedef struct _SERIAL_LINE_CONTROL {
        UCHAR StopBits;
        UCHAR Parity;
        UCHAR WordLength;
        } SERIAL_LINE_CONTROL,*PSERIAL_LINE_CONTROL;

    其中StopBits是停止位,可以是STOP_BIT_1、STOP_BITS_1_5、STOP_BITS_2等取值。

    Parity代表校验位,可以是NO_PARITY、ODD——PARITY、EVEN_PARITY、MARK——PARITY、SPACE_PARITY。WorkLength是数据位,可以是5、6、7、8。

      case IOCTL_SERIAL_GET_LINE_CONTROL: 
    		{
    			*((PSERIAL_LINE_CONTROL)(Irp->AssociatedIrp.SystemBuffer)) = pdx->Lc;
    
                Irp->IoStatus.Information = sizeof(SERIAL_LINE_CONTROL);
    			break;
    		}
    

    (4)IOCTL_SERIAL_GET_CHARS

    串口调试工具会接着向驱动发送IOCTL_SERIAL_GET_CHARS命令,这个命令是应用程序向驱动请求特殊字符,用来与控制信号握手,用数据结构SERIAL_CHARS表示。

    typedef struct _SERIAL_CHARS {
        UCHAR EofChar;
        UCHAR ErrorChar;
        UCHAR BreakChar;
        UCHAR EventChar;
        UCHAR XonChar;
        UCHAR XoffChar;
        } SERIAL_CHARS,*PSERIAL_CHARS;


    其中EofChar代表是否是传送结束、ErrorChar代码是否传送中有错误、BreadChar代码是否传送有停止等。

    (5)IOCTL_SERIAL_GET_HANDFLOW

    串口调试工具会接着向驱动发送IOCTL_SRIAL_GET_HANDFLOW命令,这个命令是负责向驱动程序获得串口驱动的握手信号,握手信号用SERIAL_HANDFLOW数据

    结构表示:

    typedef struct _SERIAL_HANDFLOW {
        ULONG ControlHandShake;
        ULONG FlowReplace;
        LONG XonLimit;
        LONG XoffLimit;
        } SERIAL_HANDFLOW,*PSERIAL_HANDFLOW;


    (6)IOCTL_SERIAL_SET_WAIT_MASK

    串口工具会接着向驱动发送IOCTL_SERIAL_SET_WAIT_MASK命令,这个命令主要是设置串口驱动的某些事件发生时,需要向应用程序通知,这些事件包括以下几种事件:

    #define SERIAL_EV_RXCHAR           0x0001  // Any Character received
    #define SERIAL_EV_RXFLAG           0x0002  // Received certain character
    #define SERIAL_EV_TXEMPTY          0x0004  // Transmitt Queue Empty
    #define SERIAL_EV_CTS              0x0008  // CTS changed state
    #define SERIAL_EV_DSR              0x0010  // DSR changed state
    #define SERIAL_EV_RLSD             0x0020  // RLSD changed state
    #define SERIAL_EV_BREAK            0x0040  // BREAK received
    #define SERIAL_EV_ERR              0x0080  // Line status error occurred
    #define SERIAL_EV_RING             0x0100  // Ring signal detected
    #define SERIAL_EV_PERR             0x0200  // Printer error occured
    #define SERIAL_EV_RX80FULL         0x0400  // Receive buffer is 80 percent full
    #define SERIAL_EV_EVENT1           0x0800  // Provider specific event 1
    #define SERIAL_EV_EVENT2           0x1000  // Provider specific event 2
    case IOCTL_SERIAL_SET_WAIT_MASK:
            {
                PIRP            pOldWaitIrp;
                PDRIVER_CANCEL  pOldCancelRoutine;
    
    			pdx->EventMask = *(PULONG)Irp->AssociatedIrp.SystemBuffer;
    
                KeAcquireSpinLock(&pdx->IoctlSpinLock, &OldIrql);
    
                pOldWaitIrp = pdx->pWaitIrp;
                if (pOldWaitIrp != NULL)
                {
                    pOldCancelRoutine = IoSetCancelRoutine(pOldWaitIrp, NULL);
    
    				//对以前没有进行完成例程的等待irp,进行完成
                    if (pOldCancelRoutine != NULL)
                    {
                        pOldWaitIrp->IoStatus.Information = sizeof(ULONG);
                        *(PULONG)pOldWaitIrp->AssociatedIrp.SystemBuffer = 0;
    
                        pOldWaitIrp->IoStatus.Status = STATUS_SUCCESS;
    
                        pdx->pWaitIrp = NULL;
                    }
                    else
                    {
                        pOldWaitIrp = NULL;
                    }
                }
    
                KeReleaseSpinLock(&pdx->IoctlSpinLock, OldIrql);
    
                if (pOldWaitIrp != NULL)
    			{
                    IoCompleteRequest(pOldWaitIrp, IO_NO_INCREMENT);
    			}
    
    			break;
    		}

    当设置新的阻塞事件时,哪果之前有等待的IRP,那么先进行完成

    (7)IOCTL_SERIAL_WAIT_ON_MASK

    这个IO控制码是最重要的一个,当串口调试工具通过前面几个IO控制码初始华好后,就会发送这个请求。

    在驱动程序中,应该阻塞在那里,即返回PENDING状态,且通过IoSetCancelRoutine设置取消例程,而不是完成这个IRP。

    当IOCTL_SERIAL_SET_WAIT_MASK设置的事件中的一项发生时,阻塞状态改为完成,并通知应用程序是哪种事件发生了。

    case IOCTL_SERIAL_WAIT_ON_MASK:
    		{
    			PDRIVER_CANCEL  pOldCancelRoutine;
    
    			KeAcquireSpinLock(&pdx->IoctlSpinLock, &OldIrql);
    
    			//等待irp一定被清除,且eventMask一定不为0
    			if ((pdx->pWaitIrp != NULL) || (pdx->EventMask == 0))
    				ntStatus = STATUS_INVALID_PARAMETER;
    			else if ((pdx->EventMask & pdx->HistoryEvents) != 0)
    			{
                    // Some events happened
                    Irp->IoStatus.Information = sizeof(ULONG);
                    *(PULONG)Irp->AssociatedIrp.SystemBuffer = pdx->EventMask & pdx->HistoryEvents;
                    pdx->HistoryEvents = 0;
                    ntStatus = STATUS_SUCCESS;
    			}else
    			{
    				pdx->pWaitIrp = Irp;
    
    				ntStatus = STATUS_PENDING;
    
    				IoSetCancelRoutine(Irp, DriverCancelWaitIrp);
    
    				if (Irp->Cancel)
    				{
    					pOldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
    
                        if (pOldCancelRoutine != NULL)
                        {
                            ntStatus = STATUS_CANCELLED;
    
                            pdx->pWaitIrp = NULL;
                        }
                        else
                        {
    						IoMarkIrpPending(Irp);
                        }
                    }
                    else
    				{
                        IoMarkIrpPending(Irp);
    				}
                }
    
                KeReleaseSpinLock(&pdx->IoctlSpinLock, OldIrql);
    			break;
    
     		}

     

    VOID DriverCancelWaitIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
    {
    	KdPrint(("DriverCancelWaitIrp\n"));
    	PDEVICE_EXTENSION pExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
        KIRQL                   OldIrql;
    
        IoReleaseCancelSpinLock(Irp->CancelIrql);
    
        KeAcquireSpinLock(&pExtension->IoctlSpinLock, &OldIrql);
    
        pExtension->pWaitIrp = NULL;        
    
        KeReleaseSpinLock(&pExtension->IoctlSpinLock, OldIrql);
    
        Irp->IoStatus.Status = STATUS_CANCELLED;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }


    关于删除例程的知识可以参考<<驱动程序的取消IRP >>

    1、3  写的实现

    串口驱动除了需要完成处理IO控制码外,还需要对读写IRP进行处理。一般情况下,作为应用程序的串口调试工具会开启多个线程,其中主线程负责与串口驱动初始化的IO控制码通信。

    另外一个很重要的线程就是发送IOCTL_SERIAL_WAIT_ON_MASK请求,对于没有数据传输的情况下,这个IO控制码请求会PENDING在那里,即阻塞。当有传送的请求时,相应的事件被触发,刚才因为IOCTL_SERAIL_WAIT_ON_MASK的IRP被阻塞的线程得以继续运行,

    如果应用程序得知该事件是被写入了一个字符,会去发出一个读请求 ,对于驱动则是读的IRP。如果循环过程,从而实现了一个虚拟摄像头回写的例子。

    在对于写IRP的派遣函数中,主要是将写的数据存储在设备扩展中,以便以后读的时候将这些内容返回到应用程序。

    另外一个很重要的内容,就是阻塞的IO控制苏醒过来。在本例中调用DriverCheckEvent函数,该函数将阻塞的IRP完成,使应用程序的线程得以继续进行。并且这个线程还知道了SERIAL_EV_RXCHAR和SERIAL_EV_RX80FULL事件的到来,从而发起一个读请求,传送到驱动中就是读IRP。

    NTSTATUS HelloWDMWrite(IN PDEVICE_OBJECT fdo,
                            IN PIRP Irp)
    {
    	KdPrint(("HelloWDMWrite\n"));
    	
        NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success
    
    	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    	// 获得当前IO堆栈
        PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );
        // 获取当前IO堆栈的操作字节数
    	ULONG DataLen = irpSp->Parameters.Write.Length;
    	// 从IRP的缓冲区中得到数据
    	PUCHAR pData = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
        KIRQL OldIrql;
        PIRP            pOldReadIrp = NULL;
        PDRIVER_CANCEL  pOldCancelRoutine;
        // 设置IRP的操作字节数
    	Irp->IoStatus.Information = 0;
    	ntStatus = STATUS_SUCCESS;
    
    	if (DataLen == 0)
        {
    		ntStatus = STATUS_SUCCESS;
        }else if (DataLen>COMBUFLEN)
        {
    		ntStatus = STATUS_INVALID_PARAMETER;
        }
    	else
    	{
    		KdPrint(("Write\n"));
            // 获取自旋锁
    		KeAcquireSpinLock(&pdx->WriteSpinLock, &OldIrql);
    		// 复制内存块
    		RtlCopyMemory(pdx->Buffer,pData,DataLen);
    
    		pdx->uReadWrite = DataLen;
    
    		if (pdx->pReadIrp != NULL) // drop it out
    		{
    			// 记录IRP
    			pOldReadIrp = pdx->pReadIrp;
    			// 设置取消函数
    			pOldCancelRoutine = IoSetCancelRoutine(pOldReadIrp, NULL);
    
    			if (pOldCancelRoutine != NULL)
    			{
    				pOldReadIrp->IoStatus.Information = 0;
    
    				pOldReadIrp->IoStatus.Status = STATUS_SUCCESS;
    
    				pdx->pReadIrp = NULL;
    			}
    			else
    			{
    				pOldReadIrp = NULL;
    			}
    
    		}
    		// 检查事件
    		DriverCheckEvent(pdx, SERIAL_EV_RXCHAR | SERIAL_EV_RX80FULL);
    
    //		DriverCheckEvent(pdx, SERIAL_EV_TXEMPTY);
    		// 释放自旋锁
            KeReleaseSpinLock(&pdx->WriteSpinLock, OldIrql);
    
    	    if (pOldReadIrp != NULL)
    		    IoCompleteRequest(pOldReadIrp, IO_NO_INCREMENT);
    	}
    
        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = DataLen;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
    
        return ntStatus;
    }


     

    VOID DriverCheckEvent(IN PDEVICE_EXTENSION pExtension, IN ULONG events)
    {
    	KdPrint(("DriverCheckEvent\n"));
        PIRP            pOldWaitIrp = NULL;
        PDRIVER_CANCEL  pOldCancelRoutine;
        KIRQL           OldIrql;
    
        KeAcquireSpinLock(&pExtension->IoctlSpinLock, &OldIrql);
    
        pExtension->HistoryEvents |= events;
    
        events &= pExtension->EventMask;
    
    	//相当于设置触发事件
        if ((pExtension->pWaitIrp != NULL) && (events != 0))
        {
            pOldWaitIrp = pExtension->pWaitIrp;
    
            pOldCancelRoutine = IoSetCancelRoutine(pOldWaitIrp, NULL);
    
            //是否已经被cancel掉?
            if (pOldCancelRoutine != NULL)
            {
                // Nein, also Request beenden
                pOldWaitIrp->IoStatus.Information = sizeof(ULONG);
                *(PULONG)pOldWaitIrp->AssociatedIrp.SystemBuffer = events;
    
                pOldWaitIrp->IoStatus.Status = STATUS_SUCCESS;
    
                pExtension->pWaitIrp      = NULL;
                pExtension->HistoryEvents = 0;
            }
            else
            {
    			//如果cancel掉,就不用IoCompleteRequest了
                pOldWaitIrp = NULL;
            }
        }
    
        KeReleaseSpinLock(&pExtension->IoctlSpinLock, OldIrql);
    
        if (pOldWaitIrp != NULL)
    	{
    		KdPrint(("complete the wait irp\n"));
            IoCompleteRequest(pOldWaitIrp, IO_NO_INCREMENT);
    	}
    }


    1、4  读的实现

    对于虚拟串口的读工作,就变得相对简单。因为写IRP会负责通知让阻塞的线程继续运行,并且通知是何种事件的来临。串口调试软件得知SERAIL_EV_RXCHAR这个事件

    ,因此发起了读事件。在驱动中,就是进入读IRP的派遣函数。

    在该派遣函数中,负责将存储在设备扩展中的数据通过IRP传送到应用程序。同时,还需要做一些同步处理。

    NTSTATUS HelloWDMRead(IN PDEVICE_OBJECT fdo,
                            IN PIRP Irp)
    {
    	KdPrint(("HelloWDMRead\n"));
    
        NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success
    
    	PDEVICE_EXTENSION pExtension = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    
        PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );
    
    	ULONG BufLen = irpSp->Parameters.Read.Length;
    	PCHAR pBuf = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
    
        KIRQL OldIrql;
    
        PDRIVER_CANCEL pOldCancelRoutine;
    
    	Irp->IoStatus.Information = 0;
    	
    	DbgPrint("DeviceObject:%08X Read\n",fdo);
    
    	if (BufLen == 0)
        {
    		ntStatus = STATUS_SUCCESS;
        }
    	else
    	{
            KeAcquireSpinLock(&pExtension->WriteSpinLock, &OldIrql);
    		// 内存复制
    		RtlCopyMemory(pBuf,pExtension->Buffer,BufLen);
    
    		Irp->IoStatus.Information = BufLen;
    
    		if (BufLen==0 && pExtension->pReadIrp==NULL) // nothing, store
    		{
    			// 保存IRP
    			pExtension->pReadIrp = Irp;
    			Irp->IoStatus.Status = ntStatus = STATUS_PENDING;
    			// 设置取消函数
                IoSetCancelRoutine(Irp, DriverCancelCurrentReadIrp);
    
    			// 重新设置取消函数
                if (Irp->Cancel)
                {
                    pOldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
    
                    if (pOldCancelRoutine != NULL)
                    {
                        // Nein, also IRP hier abbrechen
                        Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED;
    
                        pExtension->pReadIrp = NULL;
                    }
                    else
                    {
                        // 标记IRP挂起   Ja, Cancel-Routine wird Request beenden
                        IoMarkIrpPending(Irp);
                    }
    			}
    			else
    			{
                        IoMarkIrpPending(Irp);
    			}
    		}
    
            KeReleaseSpinLock(&pExtension->WriteSpinLock, OldIrql);
    	
    	}
    
        Irp->IoStatus.Status = ntStatus;
    	if (ntStatus != STATUS_PENDING)
    		IoCompleteRequest( Irp, IO_NO_INCREMENT );
    
        return ntStatus;
    }


     

     

     

    展开全文
  • 然后将驱动安装,系统中会有一个COM7的串口。将注册表修改后,这个串口就可以被发现。 使用串口调试助手的时候,这个COM可以被打开、设置和关闭。但是问题出在读写上了。 使用dbgView和IRQTrace进行调试的时候发现...
  • Windows系统上与安全软件相关的驱动开发过程中,“过滤(filter)”是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件和下层的真实驱动,就...

    在Windows系统上与安全软件相关的驱动开发过程中,“过滤(filter)”是极其重要的一个概念。过滤是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件和下层的真实驱动,就加入了新的功能。

     

    过滤的概念和基础

     

    1.设备绑定的内核API之一

    进行过滤的最主要方法是对一个设备对象(Device Object)进行绑定。通过编程生成一个虚拟设备,并“绑定”(Attach)在一个真实的设备上。一旦绑定,则本来操作系统发送给真实设备的请求,就会首先发送的这个虚拟设备。

    在WDK中,有多个内核API能实现绑定功能。下面是其中一个函数的原型:

    [cpp] view plain copy
     
    1. NTSTATUS   
    2.    IoAttachDevice(  
    3.            IN PDEVICE_OBJECT SourceDevice,  
    4.            IN PUNICODE_STRING TargetDevice,  
    5.            OUT PDEVICE_OBJECT *AttachedDevice  
    6.           );  


    IoAttachDevice的参数如下:

    SouceDevice是调用者生成的用来过滤的虚拟设备;而TargetDevice是要被绑定的目标设备。请注意这里的TargetDevice并不是一个PDEVICE_OBJECT,而是设备的名字。

    如果一个设备被其他设备绑定了,他们在一起的一组设备,被称为设备栈。实际上,IoAttachDevice总会绑定设备栈最上层的那个设备。

    AttachedDevice是一个用来返回的指针的指针。绑定成功后,被绑定的设备的指针返回到这个地址。

    下面这个例子绑定串口1。之所以这里绑定很方便,是因为在Windows中,串口设备有固定的名字。第一个串口名字为“\Device\Serial0”,第二个为“\Device\Serial1”,以此类推。请注意实际编程时C语言中的“\”要写成“\\”。

    [cpp] view plain copy
     
    1. UNICODE_STRING com_name = RLT_CONSTANT_STRING(L"\\Device\\Serial0");  
    2. NTSTATUS status = IoAttachDevice(  
    3.     com_filter_device,   //生成的过滤设备  
    4.     &com_device_name,    //串口的设备名  
    5.     &attached_device     //被绑定的设备指针返回到这里  
    6.     );  


    2.绑定设备的内核API之二

    并不是所有设备都有设备名字,所以依靠IoAttachDevice无法绑定没有名字的设备。另外还有两个API:一个是IoAttachDeviceToDeviceStack,另一个是IoAttachDeivceToDeviceStackSafe。这两个函数功能一样,都是根据设备对象的指针(而不是名字)进行绑定;区别是后者更加安全,而且只有在Windows2000SP4和Windows XP以上的系统中才有。

    [cpp] view plain copy
     
    1. NTSTATUS  
    2.   IoAttachDeviceToDeviceStackSafe(   
    3.                                       IN PDEVICE_OBJECT SourceDevice,     //过滤设备  
    4.                   IN PDEVICE_OBJECT TargetDevice,     //要被绑定的设备  
    5.                   IN OUT PDEVICE_OBJECT *AttachedToDeviceObject  //返回最终绑定的设备  
    6.                   );  


    和第一个API类似,只是TargetDevice换成了一个指针。另外,AttachedToDeviceObject同样也是返回最终被绑定的设备,实际上也就是之前设备栈上最顶端的那个设备。

     

    3.生成过滤设备并绑定

    在绑定一个设备之前,先要知道如何生成一个用于过滤的过滤设备。函数IoCreateDevice被用于生成设备:

    [cpp] view plain copy
     
    1. NTSTATUS  
    2.   IoCreateDevice(  
    3.                    IN PDRIVER_OBJECT DriverObject,  
    4.          IN ULONG DeviceExtensionSize,  
    5.          IN PUNICODE_STRING DeviceName,  
    6.          IN DEVICE_TYPE DeviceType,   
    7.          IN ULONG DeviceCharacteristics,  
    8.          IN BOOLEAN Exclusive,   
    9.          OUT PDEVICE_OBJECT *DeviceObject  
    10.          );  

    DriverObject:输入参数,每个驱动程序中会有唯一的驱动对象与之对应,但每个驱动对象会有若干个设备对象。DriverObject指向的就是驱动对象指针。

    DeviceExtensionSize:输入参数,指定设备扩展的大小,I/O管理器会根据这个大小,在内存中创建设备扩展,并与驱动对象关联。

    DeviceName:输入参数,设置设备对象的名字。一个规则是,过滤设备一般不需要设备名,传入NULL即可

    DeviceType:输入参数,设备类型,保持和被绑定的设备类型一致即可。

    DeviceCharacterristics:输入参数,设备对象的特征。

    Exclusive:输入参数,设置设备对象是否为内核模式下使用,一般设置为TRUE。

    DeviceObject:输出参数,I/O管理器负责创建这个设备对象,并返回设备对象的地址。

    但值得注意的是,在绑定一个设备之前,应该把这个设备对象的多个子域设置成和要绑定的目标对象一致,包括标志和特征。下面是一个示例函数,这个函数可以生成一个设备,并绑定到另一个设备上。

    [cpp] view plain copy
     
    1. NTSTATUS  
    2.  ccpAttachDevice(  
    3.      PDRIVER_OBJECT driver,  
    4.      PDEVICE_OBJECT oldobj,  
    5.      PDEVICE_OBJECT *fltobj,  
    6.      PDEVICE_OBJECT *next)  
    7. {  
    8.     NTSTATUS status;  
    9.     PDEVICE_OBJECT topdev = NULL;  
    10.   
    11.     //生成设备然后绑定  
    12.     status = IoCreateDevice(driver,  
    13.                             0,  
    14.                   NULL,  
    15.                           oldobj->DeviceType,  
    16.                   0,  
    17.                   FALSE,  
    18.                   fltobj);  
    19.     if (status != STATUS_SUCCESS)  
    20.     {  
    21.         return status;  
    22.     }  
    23.   
    24.     //拷贝重要标志位  
    25.     if (oldobj->Flags & DO_BUFFERED_IO)  
    26.     {  
    27.         (*fltobj)->Flags |= DO_BUFFERED_IO;  
    28.     }  
    29.     if(oldobj->Flags & DO_DIRECT_IO)  
    30.     {  
    31.         (*fltobj)->Flags |= DO_DIRECT_IO;  
    32.     }  
    33.     if (oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)  
    34.     {  
    35.         (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;  
    36.     }  
    37.     (*fltobj)->Flags |= DO_POWER_PAGABLE;  
    38.     //将一个设备绑定到另一个设备  
    39.     topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj);  
    40.     if (topdev == NULL)  
    41.     {  
    42.         //如果绑定失败了,销毁设备,返回错误  
    43.         IoDeleteDevice(*fltobj);  
    44.         *float = NULL;  
    45.         status = STATUS_UNSUCCESSFUL;  
    46.         return status;  
    47.     }  
    48.     *next = topdev;  
    49.   
    50.     //设置这个设备已经启动  
    51.     (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;  
    52.     return STATUS_SUCCESS;  
    53. }  


     

    4.从名字获得设备对象

    在知道一个设备名字的情况下,使用IoGetDeviceObjectPointer可以获得这个设备对象的指针。这个函数的原型如下:

    [cpp] view plain copy
     
    1. NTSTATUS   
    2.   IoGetDeviceObjectPointer(   
    3.                    IN PUNICODE_STRING ObjectName,  
    4.          IN ACCESS_MASK DesiredAccess,  
    5.          OUT PFILE_OBJECT *FileObject,  
    6.          OUT PDEVICE_OBJECT *DeviceObject   
    7.         );  


    其中ObjectName就是设备名字。

    DesireAccess是期望访问的权限。实际使用时不要顾虑那么多,直接填写FILE_ACCESS_ALL即可。

    FileObject是一个返回参数,即获得设备对象的同时会得到一个文件对象(File Object)。就打开串口这件事而言,这个文件对象没有什么用处。但必须注意:在使用这个函数之后必须把这个文件对象“解除引用”,否则会引起内存泄露。

     

    要得到的设备对象就返回在参数DeviceObject中了。

    [cpp] view plain copy
     
    1. //打开一个端口设备  
    2. PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)  
    3. {  
    4.     //外面输入的是串口的id,这里会改写成字符串的形式  
    5.     UNICODE_STRING name_str;  
    6.     static WCHAR name[32] = {0};  
    7.     PFILE_OBJECT fileObj = NULL;  
    8.     PDEVICE_OBJECT devObj = NULL;  
    9.   
    10.     //根据id转换成串口的名字  
    11.     memset(name,0,sizeof(WCHAR)*32);  
    12.     RtlStringCchPrintfW(  
    13.                   name,32,  
    14.                   L"\\Device\\Serial%d",id);  
    15.     RtlInitUnicodeString(&name_str,name);  
    16.   
    17.     //打开设备  
    18.     *status = IoGetDeviceObjectPointer(  
    19.                         &name_str,  
    20.                    FILE_ALL_ACCESS,  
    21.                    &fileObj,&devObj);  
    22.   
    23.     //如果打开成功了,记得一定要把文件对象解除引用  
    24.     if (*status == NT_SUCCESS)  
    25.     {  
    26.         ObDereferenceObject(fileObj);  
    27.     }  
    28.   
    29.     //返回设备对象  
    30.     return devObj;  
    31. }  

     

     

    5.绑定所有端口

    下面是一个简单的函数,实现了绑定本机上所有串口的功能。这个函数用到了前面提供的ccpOpenCom和ccpAttachDevice这两个函数

    [cpp] view plain copy
     
    1. //计算机上最多只有32个串口,这里是笔者的假定  
    2. #define CCP_MAX_COM_IO 32  
    3. //保存所有过滤设备指针  
    4. static PDEVICE_OBJECT s_fltObj[CCP_MAX_COM_IO] = {0};  
    5. //保存所有真实设备指针  
    6. static PDEVICE_OBJECT s_nextObj[CCP_MAX_COM_IO] = {0};  
    7.   
    8. //这个函数绑定所有的串口  
    9. void ccpAttachAllComs(PDRIVER_OBJECT driver)  
    10. {  
    11.     ULONG i;  
    12.     PDEVICE_OBJECT com_ob;  
    13.     NTSTATUS status;  
    14.     for(i = 0 ; i < CCP_MAX_COM_IO ; i++)  
    15.     {  
    16.         //获得object引用  
    17.         com_ob = ccpOpenCom(i,&status);  
    18.         if (com_ob == NULL)  
    19.         {  
    20.             continue;  
    21.         }  
    22.         //在这里绑定,并不管绑定是否成功  
    23.         ccpAttachDevice(driver,com_ob,&s_fltObj[i],&s_nextObj[i]);  
    24.     }  
    25. }  


    没必要关心这个绑定是否成功,就算失败,看下一个s_fltObj即可。这个数组中不为NULL的成员表示已经绑定,为NULL的成员则是没有绑定成功或者绑定失败的。这个函数需要一个DRIVER_OBJECT的指针。

     

    获得实际数据

     

    1.请求的区分

    Windows的内核开发者们确定了很多数据结构,例如:驱动对象(DriverObject),设备对象(DeviceObject),文件对象(FileObject)等,需要了解的是:

    (1)每个驱动程序只有一个驱动对象

    (2)每个驱动程序可以生成若干个设备对象,这些设备对象从属于一个驱动对象

    (3)若干个设备(他们可以属于不同的驱动)依次绑定形成一个设备栈,总是最顶端的设备先接收到请求。

    (4)IRP是上层设备之间传递请求的常见数据结构,但不是唯一的数据结构

     

    串口设备接收到的都是IRP,因此只要对所有IRP进行过滤,就可以得到串口流过的数据。请求可以通过IRP的主功能号区分。例如下面代码:

    [cpp] view plain copy
     
    1. PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);  
    2. if (irpsp->MajorFunction == IRP_MJ_WRITE)  
    3. {  
    4.     //如果是写....  
    5. }  
    6. else if (irpsp->MajorFunction == IRP_MJ_READ)  
    7. {  
    8.     //如果是读....  
    9. }  


     

     

    2.请求的结局

    对请求的过滤,最终的结局有3种:

    (1)请求被通过了,过滤不做任何事情,或者简单的获取请求的一些信息。但是请求本身不受干扰。

    (2)请求直接被否决了,下层驱动根本收不到这个请求。

    (3)过滤完成了这个请求。

     

    串口过滤要捕获两种数据:一种是发送出的数据(也就是写请求的数据),另一种是接收的数据(也就是读请求的数据)。为了简单起见,我们只捕获发送出去的请求。这样,只需要采取第一种处理方法即可。

    这种处理最为简单。首先调用IoSkipCurrentIrpStackLocation跳到当前栈空间;然后调用IoCallDriver把这个请求发送给真实的设备。请注意:因为真实的设备已经被过滤设备绑定,所以首先接收到IRP的是过滤设备对象。代码如下:

    [cpp] view plain copy
     
    1. //跳到当前栈空间  
    2. IoSkipCurrentIrpStackLocation(irp);  
    3. status = IoCallDriver(s_nextObj[i],irp);  


    3.写请求的数据

    那么,一个写请求(也就是串口一次发送的数据)保存在哪呢?IRP的结构中有三个地方可以描述缓冲区:一个是irp->MDLAddress,一个是irp->UserBuffer,一个是irp->AssociatedIrp.SystemBuffer.三种结构的具体区别参见(Windows驱动技术开发详解__派遣函数)。

    回到串口的问题,那么串口的写请求到底是用哪种方式呢?我们不知道,但是可以用下面方法获得:

    [cpp] view plain copy
     
    1. PBYTE buffer = NULL;  
    2. if (IRP->MdlAddress != NULL)  
    3.    buffer = (PBYTE)MmGetSystemAddressForMdlSafe(IRP->MdlAddress);  
    4. else  
    5.    buffer = (PBYTE)IRP->UserBuffer;  
    6. if (buffer == NULL)  
    7.    buffer = (PBYTE)IRP->AssociatedIrp.SystemBuffer;  


     

    完整的代码

     

    1.完整的分发函数(派遣函数)

    [cpp] view plain copy
     
    1. NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)  
    2. {  
    3.     PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);  
    4.     NTSTATUS status;  
    5.     ULONG i,j;  
    6.   
    7.     //首先得知道发送给哪个设备,设备一共最多CCP_MAX_COM_ID个  
    8.     //是前面的代码保存好的你都在s_fltObj中  
    9.     for (i = 0 ; i < CCP_MAX_COM_ID ; i++)  
    10.     {  
    11.         if (s_fltObj[i] == device)  
    12.         {  
    13.             //所有电源操作全部直接放过  
    14.             if (irpsp->MajorFunction == IRP_MJ_POWER)  
    15.             {  
    16.                 //直接发送,然后返回说已被处理  
    17.                 PoStartNextPowerIrp(irp);  
    18.                 IoSkipCurrentIrpStackLocation(irp);  
    19.                 return PoCallDriver(s_nextObj[i],irp);  
    20.             }  
    21.   
    22.         }  
    23.         //此外我们只过滤写请求  
    24.         if (irpsp->MajorFunction == IRP_MJ_WRITE)  
    25.         {  
    26.             //如果是写,先获得长度  
    27.             ULONG len = irpsp->Parameters.Write.Length;  
    28.             //然后获得缓冲区  
    29.             PUCHAR buf = NULL;  
    30.             if(irp->MdlAddress != NULL)  
    31.                 buf = (PUCHAR)  
    32.                 MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);  
    33.             else  
    34.                 buf = (PUCHAR)irp->UserBuffer;  
    35.             if(buf == NULL)  
    36.                 buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;  
    37.   
    38.             //打印内容  
    39.             for (j = 0 ; j < len ; j++)  
    40.             {  
    41.                 DbgPrint("comcap:Send Data:%2x\r\n",buf[j]);  
    42.             }  
    43.         }  
    44.         //这些请求直接下发即可  
    45.         IoSkipCurrentIrpStackLocation(irp);  
    46.         return IoCallDriver(s_nextObj[i],irp);  
    47.   
    48.     }  
    49.     //如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误  
    50.     irp->IoStatus.Information = 0;  
    51.     irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;  
    52.     IoCompleteRequest(irp,IO_NO_INCREMENT);  
    53.     return STATUS_SUCCESS;  
    54.   
    55. }  


    2.动态卸载

    前面说了如何绑定,但是没说如何解除绑定。如果要把这个模块做成可以动态卸载的模块,则必须提供一个卸载函数。我们应该在卸载函数中完成解除绑定的功能,否则,一旦卸载一定会蓝屏。

    [cpp] view plain copy
     
    1. #define  DELAY_ONE_MICROSECOND  (-10)  
    2. #define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)  
    3. #define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)  
    4.   
    5. VOID ccpUnload(PDRIVER_OBJECT drv)  
    6. {  
    7.     ULONG i;  
    8.     LARGE_INTEGER interval;  
    9.   
    10.     //首先解除绑定  
    11.     for (i = 0 ; i < CCP_MAX_COM_ID ; i++)  
    12.     {  
    13.         if(s_nextObj[i] != NULL)  
    14.             IoDeleteDevice(s_nextObj[i]);  
    15.     }  
    16.   
    17.     //睡眠5秒,等待所有IRP处理结束  
    18.     interval.QuadPart = (5*1000 *DELAY_ONE_MICROSECOND);  
    19.     KeDelayExecutionThread(KernelMode,FALSE,&interval);  
    20.   
    21.     //删除这些设备  
    22.     for (i = 0 ; i < CCP_MAX_COM_ID ; i++)  
    23.     {  
    24.         if(s_fltObj[i] != NULL)  
    25.             IoDeleteDevice(s_fltObj[i]);  
    26.     }  
    27. }  


    DriverEntry函数代码:

    [cpp] view plain copy
     
    1. NTSTATUS DriverEntry(  
    2.     IN OUT PDRIVER_OBJECT   DriverObject,  
    3.     IN PUNICODE_STRING      RegistryPath  
    4.     )  
    5. {  
    6.     DbgPrint("Enter Driver\r\n");  
    7.     size_t i;  
    8.     //所有分发函数都设置成一样的  
    9.     for (i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION ; i++)  
    10.     {  
    11.         DriverObject->MajorFunction[i] = ccpDispatch;  
    12.     }  
    13.     //支持动态卸载  
    14.     DriverObject->DriverUnload = ccpUnload;  
    15.   
    16.     //绑定所有的串口  
    17.     ccpAttachAllComs(DriverObject);  
    18.   
    19.     return STATUS_SUCCESS;  
    20. }  


    测试效果:

    转载于:https://www.cnblogs.com/dancheblog/p/6050665.html

    展开全文
  • Windows CE是一个开放的、可升级、可裁减的32位实时嵌入式操作系统,...在同时要求高性能和低功耗的嵌入式应用中,运行Win-dows CE的LPC3250平台将会有很好的市场前景,对于常用到的串口驱动开发显得尤为重要。  1
  • 同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书*的特色在于每一节的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手实验...
  • Windows CE是一个开放的、可升级、可裁减的32位实时嵌入式操作系统,具有可靠...在同时要求高性能和低功耗的嵌入式应用中,运行Win-dows CE的LPC3250平台将会有很好的市场前景,对于最常用到的串口驱动开发显得尤为重
  • 在驱动程序开发中,经常需要一个驱动...这时就需要在虚拟串口驱动程序中调用USB驱动程序。 同步调用方法 本章节假设DriverA是将要被调用的目标驱动程序。 DriverB可以有多种方法调用DriverA,可以是同...
  • 1.3.4 设置VMware的管道虚拟串口 11 1.3.5 设置Windows内核符号表 12 1.3.6 实战调试first 13 第2章 内核编程环境及其特殊性 16 2.1 内核编程的环境 16 2.1.1 隔离的应用程序 16 2.1.2 共享的内核空间 17 ...
  • 该书由浅入深、循序渐进地介绍了Windows驱动程序的开发方法与调试技巧。本书最大的特色在于每一节...同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍。
  • 虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书最大的特色在于每一节 的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手实验,掌握各类Windows驱动 程序的开发技巧...
  • 1.3.4 设置VMware的管道虚拟串口 11 1.3.5 设置Windows内核符号表 12 1.3.6 实战调试first 13 第2章 内核编程环境及其特殊性 16 2.1 内核编程的环境 16 2.1.1 隔离的应用程序 16 2.1.2 共享的内核空间 17 ...
  • widnows 驱动

    2009-12-03 19:36:54
    同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书最大的特色在于每一节的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手...
  • 本文讲了如何使用VS2019在VMware虚拟机上进行双机调试。 环境:VS2019 Community + Windows10专业版 虚拟机环境(VMware Workstation 16 Pro):Windows10家庭版 VMware虚拟机可以直接在官网下载(密钥可以直接百度搜到...
  • visual C++_Turbo C串口通信编程实践

    热门讨论 2010-07-30 09:14:24
    1.3.4 虚拟串口-为计算机添加取之不尽的串口资源 1.4 使用串口调试助手来体验串口通信 1.5体验Windows环境下Visual C++串口通信编程 1.6体验DOS环境下Turbo C串口通信编程 第2章 VC多线程串口编程工具...
  • 如果该类模块做的足够完善,可以提供一个windows系统的设备驱动,安装后,在windows系统上就可以看到虚拟出的串口了。不过这样做,虽然简便了开发,但是性能有些问题,所以有的模块还支持直接用TC...
  • Windows串口与键盘过滤驱动Windows虚拟存储设备与存储设备过滤驱动Windows文件系统过滤驱动、文件系统透明加密/解密驱动Windows各类网络驱动(包括TDI过滤驱动及3类NDIS驱动),以及最新的WDF驱动开发模型。...

空空如也

空空如也

1 2 3 4 5
收藏数 98
精华内容 39
关键字:

windows开发虚拟串口驱动