精华内容
下载资源
问答
  • windows开发虚拟串口驱动
    2022-06-18 09:01:13

    在windows系统中有微软提供了很多虚拟设备驱动,很多商业软件也存在虚拟总线方式实现各种产品,如虚拟串口,虚拟光驱,虚拟USB(HID),虚拟网卡等等,实现方式有多种,微软也提供了相应框架,下面简单介绍一种最常用,最灵活,很多商业软件也常用的方法:虚拟总线+虚拟设备的方式,只简介实现思路

    虚拟总线驱动实现,建议参考微软wdk/ddk提供的源码,Toaster提供了全套学习代码,其中虚拟总线驱动很典型,bus目录下有wdm/kmdf两种版本,很多人看不太明白实现原理,总体上实现思路是:(1)在addDevice中创建虚拟总线设备,(2)在IRP_MJ_DEVICE_CONTROL中模拟插上设备,根据不同HardwrieID创建PDO,(3)创建FDO,要在IRP_MJ_SYSTEM_CONTROL处理中,模拟真实设备送给系统相应数据,这一步很关键,FDO的实现可以是独立一个功能驱动,也可以在总线驱动内部创建,象Elitama虚拟串口驱动,就是独立的虚拟总线+独立的虚拟设备实现,很多个性化产品,直接在总线处理中处理,如微软提供的蓝牙总线代码,usbip和虚拟x360手柄驱动等等,各有利弊端。

    明白总线驱动与设备驱动原理,会发现实现虚拟总线驱动并不是太难,难在虚拟设备驱动实现,因为需要熟悉相应设备的物理背景,如虚拟USB,要熟悉USB协议,如虚拟串口,要熟悉串口协议,当然实现时,不必将所有协议都实现,让操作系统误以为是真设备,不报错就行

    第一次写技术博客,真不知怎么写,感觉提供思路更重要,快速理清代码是关键!以后有空再根据代码介绍

    更多相关内容
  • windows xp操作系统下开发虚拟串口驱动
  • Windows 2000下虚拟串口 WDM 驱动程序的开发 孙筱萌,夏 斌,韩德红,方 晓 (空军雷达学院电子对抗系,武汉 430019 摘 要:针对传统 RS-232串行通信存在的通信距离 端口数量等多个方面的限制, 提出了在 Windows 2000 操作...
  • 该软件是霍尼韦尔扫描枪usb转串口的驱动程序,适用于32位和64位的操作系统,是串口开发及读取条形码扫描枪数据的必备工具
  • 针对传统RS.232串行通信存在的通信距离、端口数量等多个方面的限制,提出了在Windows2000操作系统下使用DriverStudio开发工具编写符合WDM模式的虚拟串口设备驱动程序结构和实现方法.使用结果表明,此设计方法可以...
  • windows虚拟串口驱动开发windows下驱动程序设计。
  • 虚拟串口设备驱动

    2022-05-08 10:50:30
    字符设备驱动除了前面搭建好的框架外,接下来最重要的是实现设备的操作方法。 #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #...

    前面内容:
    1 Linux驱动—内核模块基本使用

    2 Linux驱动—内核模块参数,依赖(进一步讨论)

    3 字符设备驱动

    先学习下虚拟串口设备是啥?

    虚拟串口设备

    在进一步实现字符设备驱动之前,我们先来讨论一下这本书中用到的一个虚拟串口设备。这个设备是驱动代码虚拟出来的,不能实现真正的串口数据收发,但是它能够接收用户想要发送的数据,并且将该数据原封不动地环回给串口的收端,使用户也能从该串口接收数据。也就是说,该虚拟串口设备是一个功能弱化之后的只具备内环回作用的串口,如图3.3所示。
    在这里插入图片描述

    这一功能的实现

    主要是在驱动中实现了一个FIFO,驱动接收用户层传来的数据,然后将之放入FIFO,当应用层要获取数据时,驱动将FIFO中的数据读出,然后复制给应用层。
    一个更贴近实际的形式应该是在驱动中有两个FIFO,一个用于发送,一个用于接收,但是这并不是实现这个简单的虛拟串口设备驱动的关键,所以为了简单起见,这里只用了一个FIFO。

    内核中已经有了一个关于FIFO的数据结构struct kfifo,相关的操作宏或函数的声明、
    定义都在“include/inux/kfifo.h"头文件中,下面将最常用的宏罗列如下。

    DEFINE_KFIFO(fifo,type,size)
    kfifo_from_user(fifo,from,len,copied)
    kfifo_to_user(fifo,to,len,copied)
    

    DEFINE_ KFIFO用于定义并初始化一个FIFO,这个变量的名字由fifo参数决定, type是FIFO中成员的类型size 则指定这个FIFO有多少个元素但是元素的个数必须是2的幂

    kfifo_from_user 是将用户空间的数据(from) 放入FIFO中,元素个数由len来指定,实际放入的元素个数由copied返回

    kfifo_to_user 则是将FIFO中的数据取出,复制到用户空间(to)。 len 和copied的含义同kfifo_ from_ user 中对应的参数。

    虚拟串口设备驱动

    字符设备驱动除了前面搭建好的框架外,接下来最重要的是实现设备的操作方法。

    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/kfifo.h>
    
    #define VSER_MAJOR	256
    #define VSER_MINOR	0
    #define VSER_DEV_CNT	1
    #define VSER_DEV_NAME	"vser"
    
    static struct cdev vsdev;
    DEFINE_KFIFO(vsfifo, char, 32);
    
    static int vser_open(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    static int vser_release(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
    {
    	unsigned int copied = 0;
    
    	kfifo_to_user(&vsfifo, buf, count, &copied);
    
    	return copied;
    }
    
    static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
    {
    	unsigned int copied = 0;
    
    	kfifo_from_user(&vsfifo, buf, count, &copied);
    
    	return copied;
    }
    
    static struct file_operations vser_ops = {
    	.owner = THIS_MODULE,
    	.open = vser_open,
    	.release = vser_release,
    	.read = vser_read,
    	.write = vser_write,
    };
    
    static int __init vser_init(void)
    {
    	int ret;
    	dev_t dev;
    
    	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
    	if (ret)
    		goto reg_err;
    
    	cdev_init(&vsdev, &vser_ops);
    	vsdev.owner = THIS_MODULE;
    
    	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
    	if (ret)
    		goto add_err;
    
    	return 0;
    
    add_err:
    	unregister_chrdev_region(dev, VSER_DEV_CNT);
    reg_err:
    	return ret;
    }
    
    static void __exit vser_exit(void)
    {
    	
    	dev_t dev;
    
    	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
    
    	cdev_del(&vsdev);
    	unregister_chrdev_region(dev, VSER_DEV_CNT);
    }
    
    module_init(vser_init);
    module_exit(vser_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
    MODULE_DESCRIPTION("A simple character device driver");
    MODULE_ALIAS("virtual-serial");
    

    新的代码驱动在代码DEFINE_KFIFO(vsfifo, char, 32);定义并初始化了一个名叫vsfifo的structu kfifo对象,每个对象的类型为char,共有32个元素的空间。

    static int vser_open(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    
    static int vser_release(struct inode *inode, struct file *filp)
    {
    	return 0;
    }
    

    代码实现了设备的打开和关闭函数,分别对应于file_operations 内的openrelease方法。
    因为是虚拟设备,所以这里并没有需要特别处理的操作,仅仅返回0表示成功。
    这两个函数都有两个相同的形参,

    1. 第一个形参是要打开或关闭文件的inode,
    2. 第二个形参则是打开对应文件后由内核构造并初始化好的file 结构

    在前面的章节中我们已经较深入地分析了这两个对象的作用。
    在这里之所以叫release而不叫close是因为一个文件可以被打开多次,那么vser_ open函数相应地会被调用多次,但是关闭文件只有到最后一个close操作才会导致vser_ release函数被调用,所以用release更贴切。

    代码

    static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
    {
    	unsigned int copied = 0;
    
    	kfifo_to_user(&vsfifo, buf, count, &copied);
    
    	return copied;
    }
    

    是read系统调用的驱动实现,这里主要把FIFO中的数据返回给用户层,使用了kfifo_to_user这个宏。返回给用户层。
    read 系统调用要求用户返回实际读取的字节数,而copied变量的值正好符合这一要求。

    代码第36行到第43行是对应的write系统、

    static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
    {
    	unsigned int copied = 0;
    
    	kfifo_from_user(&vsfifo, buf, count, &copied);
    
    	return copied;
    }
      
    

    调用的驱动实现,同read系统调用一样,只是数据流向相反而已。

    读和写函数引入了3个新的形参,分别是buf, count 和pos,根据上面的代码,已经不难发现它们的含义。buf 代表的是用户空间的内存起始地址; count 表示用户想要读写多少个字节的数据:而pos是文件的位置指针,在虚拟串口这个不支持随机访问的设备中,该参数无用。_user是提醒驱动代码编写者,这个内存空间属于用户空间。

    代码

    static struct file_operations vser_ops = {
    	.owner = THIS_MODULE,
    	.open = vser_open,
    	.release = vser_release,
    	.read = vser_read,
    	.write = vser_write,
    };
    
    

    这里是将file_operations 的函数指针分别指向上面定义的函数
    你看 .read指向vser_read函数

    这样在驱动这样在应用层发生相应的系统调用后,在驱动里面的函数就会被相应地调用

    上面这个示例实现了一个功能非常简单,但是基本可用的虚拟串口驱动程序。

    按照下面的步骤可以进行验证。

    先创建设备号

    sudo mknod /dev/vser0 c 256 0 
    

    在这里插入图片描述

    然后编译运行
    make
    make modules_install
    sudo modpeobe vser
    在这里插入图片描述

    然后 往这个设备中dev/vser0写入数据
    echo "vser deriver test" > /dev/vser0
    然后查看
    cat /dev/vser0
    最后
    会出现
    vser deriver test

    通过实验结果可以看到,对/dev/vser0 写入什么数据,就可以从这个设备读到什么数据,和一个具备内环回功能的串口是一致的。

    为了方便读者对照查阅,特将file_operations 结构类型的定义代码列出。从中我们可以看到,还有很多接口函数还没有实现,在后面的章节中,我们会陆续再实现一些接口。

    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*mremap)(struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
    #ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
    #endif
    };
    

    显然,一个驱动对下面的接口的实现越多,它对用户提供的功能就越多,但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问,那么lseek函数接口自然就不用实现。

    展开全文
  • WindowsCE是一个开放的、可升级、可裁减的32位实时嵌入式操作系统,...在同时要求高性能和低功耗的嵌入式应用中,运行Win-dowsCE的LPC3250平台将会有很好的市场前景,对于最常用到的串口驱动开发显得尤为重要。Wind
  • 虚拟串口设备驱动,虚拟串口的完整源代码,包括注释和开发日志。据说市值5万人民币!Virtual Serial Device DriverWindows2K下虚拟串口设备驱动程序及相关应用程序内容列表===========主要功能使用说明各模块说明工作...

    虚拟串口设备驱动,虚拟串口的完整源代码,包括注释和开发日志。据说市值5万人民币!

    Virtual Serial Device Driver

    Windows2K下虚拟串口设备驱动程序及相关应用程序内容列表

    ===========主要功能

    使用说明

    各模块说明

    工作机制

    更新和改进

    不足之处

    跟我联系主要功能

    ===========在Windows2K操作系统上虚拟出若干串口设备,这些虚拟串口设备作为 TDI Client 将串口应用程序(比如超级终端)读写串口设备的操作通过网络的方式传递到远端,即将应用程序写入的数据以UDP协议发送到远端目标机器的指定端口;监听本机指定端口,将所有在此端口收到的UDP报文数据提交给应用程序。1. 在操作系统中,虚拟出若干串口。可以使用串口工具(比如操作系统自带的超级终端软件)对串口进行打开、发送、接收、关闭等操作。

    2. 由于是虚拟串口,所以可以设置任意波特率、数据位、奇偶校验、停止位等端口操作。

    3. 虚拟串口创建以后,打开本机的6001端口,设置接收回调函数用于接收此端口上的数据。应用程序写入虚拟串口的数据则直接发送到远端指定IP地址和端口。

    使用说明

    ===========1. 安装busdriver目录中提供的toaster总线驱动程序:BusEnum.sys。安装步骤详见DDK的toasterbus例子。

    2. 控制台上运行enum -p 1,对新的硬件设备指定 inf目录,安装inf目录中提供的本虚拟串口驱动程序。安装完毕以后在“设备管理器”的"端口(COM和LPT)"中应该多出新的设备。enum程序由DDK的toasterexeenum例子编译。只是改动了toasterincpublic.h中的#define BUS_HARDWARE_IDS L"ToasterMsToaster"

    3. 控制台上运行test.exe,创建/删除虚拟串口设备的DOS名,设定远端目标机器的IP地址和端口。

    4. 卸载设备可以在控制台上运行 enum -u 1,此操作代表强行拔出设备。另一个方法是点击桌面右下角系统托盘中的“拔下或弹出硬件”图标来停止设备的工作。

    6. 更新驱动程序时,只需要将所有的虚拟串口卸载,然后再编译驱动程序,build -ceZ,而后将编译后的.sys文件拷贝到system32drivers目录即可。

    虚拟串口设备驱动完整源代码内附测试工具注意事项

    -------

    1. 本项目中所有编译后的驱动和应用程序都在inf目录下。在Win2K(sp4)平台上测试的较多,在Xp(sp2)平台上作过简单测试。没有在多CPU下进行测试。由于Win98下串口的结构跟Win2k以后差别较大,所以本程序不能在Win98下使用。

    2. 简单起见,绑定本机端口这一步没有做成动态的。只是用一个静态变量从6001开始累加来指定本机端口。创建多个虚拟串口设备的时候,每一个虚拟串口设备依次绑定本机6001,6002,6003等以后端口。因此远端UDP发送程序可以分别对这些端口发送数据来进行测试。

    3. 发送到远端的指定端口,这一步可以动态实现,即通过test程序给每一个虚拟串口设备指定端口。

    4. 编译win2K和Xp平台下的驱动程序应该采用相应的编译环境。在Win2000编译环境下编译的.sys文件在Xp+sp2下可能有点小问题。反之没有做过测试。各模块说明

    ===========busdriver toaster总线的安装文件,包括inf文件和 .sys文件

    exe  串口应用程序,控制虚拟串口设备。参考其目录的说明.txt

    inc  application 和 sys都要用到的若干宏定义和其他

    inf  最终安装所需要的.inf .sys 和应用程序

    log&debug 开发日志,说明文档

    others  开发辅助软件,例子和测试工具。

    sys  WDM类型的驱动程序,虚拟串口驱动程序的主体

    工作机制

    ===========虚拟设备的创建

    ----------------跟商业软件Virtual Serial Ports Driver XP3类似。

    首先利用DDK的toaster bus例子安装toaser bus总线,然后利用该总线驱动程序生成一个(或几个)虚拟PDO。对这些虚拟的PDO,加载这个虚拟串口驱动程序。

    注意:toaster bus driver安装以后不会自动生成虚拟设备PDO,需要运行相应的enum程序。

    “enum -p 1“会生成一个新的虚拟设备。该设备的Hardware ID基于NTDDKsrcgeneraltoasterincpublic.h里的#define BUS_HARDWARE_IDS L"ToasterMsToaster"的定义。这个Hardware ID和我们的虚拟串口驱动程序inf文件(MySerial.inf)里边指定的Hardware ID必须一致。否则安装本驱动程序的时候,会提示“未发现相关的硬件信息”。多个虚拟设备的创建:

    在AddDevice中设置静态变量,每次虚拟设备创建、加载驱动程序都会调用该函数。该静态变量累加。生成的FDO的名字由DeviceSerial11开始递增

    每一个虚拟设备都可以采用1~255之间没有被占用的数字来动态的生成DOS名,即COMn。

    具体操作参考exe目录下的readme.txt读写方法

    ----------------

    1. 读数据采用缓冲区方式

    设备在启动时打开本机UDP协议的6001端口,设置接收回调函数将该端口上收到的所有数据拷贝到接收缓冲区。接收缓冲区是一个环形缓冲区,保存接收到的最新数据。应用程序若不及时读取,数据可能会被最近接收到的数据所刷新。2. 写数据采用直接发送方式

    应用程序向设备写的数据直接以UDP协议发送到之前设定的远端IP地址和端口上。此过程为阻塞式。不同于读数据方式,没有专门为写数据设置缓冲区。串口相关DeviceIoContrl的处理

    ----------------

    着重处理

    IOCTL_SERIAL_GET_COMMSTATUS

    IOCTL_SERIAL_GET_WAIT_MASK

    IOCTL_SERIAL_SET_WAIT_MASK

    IOCTL_SERIAL_WAIT_ON_MASK

    IOCTL_SERIAL_PURGE

    简单处理

    IOCTL_SERIAL_SET_BAUD_RATE

    IOCTL_SERIAL_GET_BAUD_RATE

    IOCTL_SERIAL_SET_LINE_CONTROL

    IOCTL_SERIAL_GET_LINE_CONTROL

    IOCTL_SERIAL_SET_TIMEOUTS

    IOCTL_SERIAL_GET_TIMEOUTS

    IOCTL_SERIAL_SET_CHARS

    IOCTL_SERIAL_GET_CHARS

    IOCTL_SERIAL_SET_QUEUE_SIZE

    IOCTL_SERIAL_GET_HANDFLOW

    IOCTL_SERIAL_SET_HANDFLOW

    其余IOCTL则直接返回IOCTL_SERIAL_GET_COMMSTATUS

    返回CommsPorts的状态,即deviceExtension->SerialStatus。应用程序根据这里边的AmountInInQueue和AmountInOutQueue来判断是否有接收/发送缓冲区里边是否有数据。IOCTL_SERIAL_GET_WAIT_MASK

    返回驱动当前等待的Mask,返回值应该与上一次Set_Wait_Mask保持一致

    IOCTL_SERIAL_SET_WAIT_MASK

    设置驱动应该等待的Mask。此时驱动应该取消以前Pending的ReadIrp和WaitOnMaskIrp。

    IOCTL_SERIAL_WAIT_ON_MASK

    根据当前的WaitMask来判断,当设备出现WaitMask上的事件应该返回该Irp;若没有WaitMask上的事件发生,则IoMarkIrpPending,返回Status_Pending。以后某个时间出现WaitMask事件以后再返回该Irp

    IOCTL_SERIAL_PURGE

    取消指定的Rx/Tx的Irp。更新和改进

    =============

    1、摒弃了上一版本的双串口互连的实现。本版本将虚拟串口设备作为TDI Client通过网络与远端进行数据交换。网络通信中采用的是UDP协议,所以不能保证数据传输的可靠性和顺序性。若对上述性能有要求,可以采用TCP协议建立一个 Connection-Oriented 连接。具体实现请参考DDK文档。

    2、串口设备的枚举,虚拟串口设备的DosName的创建和删除,远端目标机器的IP地址和端口,目前均由DeviceIoControl来负责控制。因此enum -p 1创建虚拟串口设备以后,若不运行test.exe文件,很多串口应用程序可能无法找到该虚拟串口设备。关于test.exe的使用参考相应目录下的文档。

    3、较上一版本,本次对串口相关的DeviceIoContrl进行了更多的处理,主要是如下

    IOCTL_SERIAL_GET_COMMSTATUS,IOCTL_SERIAL_GET_WAIT_MASK,IOCTL_SERIAL_SET_WAIT_MASK,IOCTL_SERIAL_WAIT_ON_MASK,IOCTL_SERIAL_PURGE,IOCTL_SERIAL_SET_TIMEOUTS。

    4、在读数据中,若当前缓冲区里边没有数据,则设定一个DPC,启动定时器。根据IOCTL_SERIAL_SET_TIMEOUTS设定的超时值作简单的超时处理。

    5、对于如何将设备出现在“拔出或弹出硬件”对话框里边(如同U盘一样),可参考PnpQueryCapabilities函数的实现。

    6、TDI的相关操作均在TDIClient.c文件中。这部分主要参考的代码是TDIClient和TDISample两个项目。不足之处

    =============1. 对于多线程应用程序读写虚拟串口的情况,支持的不够好。鉴于目前大部分串口应用程序都是采用单线程读写设备,所以实际中影响不大。

    2. 串口的超时处理比较简单。

    3. 网络TDI Client方面,仅实现了UDP的无连接服务,网络通信质量不高。目前设备启动后就固定的监听某一端口,该端口是硬编码到驱动程序里边,不够灵活。

    =============

    自从上一版虚拟串口程序发布以后,一年以来陆续的收到了很多网友的来信。有鼓励的,有报告bug的,还有很多提出设计思路的。总计不下30余人,这里一并表示感谢!

    作为一个虚拟串口设备驱动,本身没有太多的应用场合,称之为“玩具软件”还算比较恰当。所实现的功能基本上属于演示性质的。串口部分对上一版进行了一些完善,TDI Client部分则基本上是从others下提供的TDI Sample等代码中略加修改移植过来。所以对于驱动开发的新手来说还算有点参考价值。对于中高手而言,不如直接看others下的源代码更有收获。

    展开全文
  • 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数据结构表示。

    [cpp]  view plain copy
    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。

    [cpp]  view plain copy
    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表示。

    [cpp]  view plain copy
    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数据

    结构表示:

    [cpp]  view plain copy
    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命令,这个命令主要是设置串口驱动的某些事件发生时,需要向应用程序通知,这些事件包括以下几种事件:

    [cpp]  view plain copy
    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  
    [cpp]  view plain copy
    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设置的事件中的一项发生时,阻塞状态改为完成,并通知应用程序是哪种事件发生了。

    [cpp]  view plain copy
    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.         }  

     

    [cpp]  view plain copy
    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。

    [cpp]  view plain copy
    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. }  


     

    [cpp]  view plain copy
    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传送到应用程序。同时,还需要做一些同步处理。

    [cpp]  view plain copy
    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. }
    展开全文
  • 开发虚拟串口驱动程序

    千次阅读 2008-12-10 19:53:00
    本文作者给出了一种在Windows平台上实现虚拟串口的方法,由此实现的“串口”具有真实串口完全相同的系统调用接口。 在很多应用中需要用到虚拟串口,如在Modem卡出现之前,已经有了接在计算机串口上的外部Modem,而且...
  • 虚拟串口驱动

    2011-11-08 13:16:27
    很不错的虚拟串口驱动哦,单机上不用连线就可以使用多个串口,大大方便开发咯。
  • USB虚拟串口使用时应注意: 用于上拉的控制位使能时,主机能检测到USB设备。 设备插入时,地址为0,后由主机分配地址。 枚举结束前(即设备被识别前),不能中断程序,否则设备不能被识别。 设备发送usb_...
  • windows虚拟网卡驱动开发

    万次阅读 热门讨论 2017-04-06 19:05:24
    by fanxiushu 2017-04-06 转载或...很早前的文章介绍过windows和linux平台的虚拟网卡技术, 详见 http://blog.csdn.net/fanxiushu/article/details/8526708 http://blog.csdn.net/fanxiushu/article/details/8526
  • 开发篇介绍了在实际工作中可能遇到的各种开发需求的技术实现,包括:串口的过滤、键盘的过滤、磁盘的虚拟、磁盘的过滤、文件系统的过滤与监控、文件系统透明加密、文件系统微过滤驱动、网络传输层过滤、Windows过滤...
  • Windows CE是一个开放的、可升级、可裁减的32位实时嵌入式操作系统,具有可靠...在同时要求高性能和低功耗的嵌入式应用中,运行Win-dows CE的LPC3250平台将会有很好的市场前景,对于最常用到的串口驱动开发显得尤为重
  • 但是windows操作系统不像Linux操作系统,它的代码不开源,导致这方面的资料很少,因此也找寻了很长时间,最终找到了两款开源项目的虚拟接口驱动: Wireguard项目的Wintun接口 openVPN的Tap接口 ...
  • 开发篇介绍了在实际工作中可能遇到的各种开发需求的技术实现,包括:串口的过滤、键盘的过滤、磁盘的虚拟、磁盘的过滤、文件系统的过滤与监控、文件系统透明加密、文件系统微过滤驱动、网络传输层过滤、Windows过滤...
  • Windows驱动开发详解

    2019-01-16 15:12:42
    同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书最大的特色在于每一节的例子都是经过精挑细选的,具有很强的针对性。 力求让读者通过亲自动手...
  • 1 usb转串口驱动驱动开发网tiamo的usb2com】 2 cp2102的比较全的资料 3 张帆《windows驱动开发技术详解》的随盘代码 4 ark3116的linux驱动 windows驱动代码 5 cp210x系列的linux驱动代码
  • 同时,还针对流行的PCI驱动程序、USB驱动程序、虚拟串口驱动程序、摄像头驱动程序、SDIO驱动程序进行了详细的介绍,本书*的特色在于每一节的例子都是经过精挑细选的,具有很强的针对性。力求让读者通过亲自动手实验...
  • 串口驱动开发学习

    千次阅读 2017-01-04 22:11:39
    串口过滤驱动learning ----------------------------------------------------------------------------------Cherry-2016-5-31-----------------------   先看一张图,是我按照内核安全与驱动开发>内容编写编译...
  •    //========================================================================  //TITLE:  // WinCE虚拟串口驱动(一)  //AUTHOR:  // norains
  • 几款优秀的 Windows 虚拟串口模拟器

    千次阅读 2020-05-15 10:18:25
    来源:网络编辑整理:strongerHuang大家用的较多的可能是USB虚拟串口,相比串口传输速度更快。而本文分享的是基于Windows虚拟串口虚拟串口(虚拟 COM 端口),应该很...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,307
精华内容 2,522
热门标签
关键字:

windows开发虚拟串口驱动