首页 > 系统相关 >windows 驱动实例分析系列: PL2303芯片开发实战之二

windows 驱动实例分析系列: PL2303芯片开发实战之二

时间:2024-12-26 22:55:20浏览次数:5  
标签:status USB WDF windows pContext PL2303 之二 线程 NULL

经过对linux代码的解读和在windows下面的简单测试,已经总结出来PL2303的基本使用方式了,接下来就可以进行windows平台下的驱动开发了,同时还需要一个com驱动的例子,幸运的是之前已经写过了:

windows 驱动实例分析系列-PL2303芯片开发实战之一

windows 驱动实例分析系列-定时日志的COM驱动

可以参考上面的文章。

驱动创建

之前的COM驱动是umdf的,但是这里也许涉及中断,毕竟Linux代码中有中断的处理,和之前的日志驱动不一样,这次的驱动是需要真实解决问题的,所以似乎使用kmdf更好一些,虽然我不觉得这个GPS产品有什么好中断的,但它确实存在中断端点。

下面是创建一个kmdf USB驱动:

706844c4fdc449a58a9cb123a3bcf0df.png

数据上报问题使用com端口上报即可,还是可以使用通用串口软件打开就行。不过,似乎设置波特率的接口暴露出来没什么意义,因为它只在波特率等于115200(0x1C200)的时候可以上报数据,其它波特率会一直输出0,那么直接屏蔽波特率的输出就可以了。

KMDF框架解释

在KMDF的USB框架中,驱动的初始化流程如下:

  • 1. DriverEntry代码比较简单,填充AddDevice之后调用WdfDriverCreate函数即可;
  • 2. AddDevice函数则比较复杂:
  • 2.1. 填充并调用WdfDeviceInitSetPnpPowerEventCallbacks函数设置回调;
  • 2.2 调用WdfDeviceInitSetIoType设置I/O模式,这一步是可选的;
  • 2.3 调用WdfDeviceCreate函数创建设备对象;
  • 2.4 调用WdfDeviceSetPnpCapabilities设置PNP;
  • 2.5 初始化并填充设备扩展;
  • 2.6 调用WdfDeviceCreateDeviceInterface来创建基于GUID_DEVINTERFACE_COMPORT的设备接口;
  • 2.7 调用WdfDeviceOpenRegistryKey函数获取系统分配的COM编号,并使用这个编号生成符号链接字符串;
  • 2.8 调用WdfDeviceCreateSymbolicLink创建符号链接;
  • 2.9 调用WdfIoQueueCreate创建默认队列;
  • 2.10 调用WdfIoQueueCreate作为手动读队列;
  • 2.11 调用WdfIoQueueCreate作为等待掩码队列;
  • 3. 系统紧接着调用EvtDevicePrepareHardware,这里会初始化USB相关的部分:
  • 3.1 调用WdfUsbTargetDeviceCreateWithParameters函数创建WDFUSBDEVICE;
  • 3.2 调用WdfUsbTargetDeviceSelectConfig选择配置,由于这个设备的特殊性,只有一个配置一个接口,所以无需设置使用的接口;
  • 3.3 调用WdfUsbInterfaceGetConfiguredPipe来获取所有的端点并保存下来,这个设备有三个端点,分别是读、写、中断三个端点;
  • 3.4 调用WdfDeviceAssignS0IdleSettings设置S0;
  • 3.5 调用WdfDeviceAssignSxWakeSettings设置Sx唤醒;
  • 3.6 分配并初始化可能使用的缓冲区;
  • 4. D0Entry函数意味着正式开始I/O处理:
  • 4.1 根据Linux的定义设置配置,写那些命令;
  • 4.2 根据定义设置波特率等参数;
  • 4.3 创建内核线程连续读取端点上的数据;
COM编号

在INF文件安装之后,系统会根据总线驱动程序和安装驱动程序的处理,在注册表中注册一些值,这些值将可以在调用DriverEntry时被驱动文件访问。

在我们现在的案例中,就是如何根据COM编号来创建符号链接:

NTSTATUS CreateSymbolName(WDFDEVICE Device)
{
    NTSTATUS status = 0;

    UNICODE_STRING portName;
    UNICODE_STRING comPort;
    UNICODE_STRING symbolicLinkName;

    WDFKEY  key;

    WCHAR   Buffer[COM_NAME_SIZE] = { 0 };
    WCHAR   NameBuffer[SYMBOL_NAME_SIZE] = { 0 };

    LPGUID  guid = (LPGUID)&GUID_DEVINTERFACE_COMPORT;

    // 1. 创建设备接口
    status = WdfDeviceCreateDeviceInterface(
        Device, 
        guid, 
        NULL);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // 2. 从注册表中读取COM端口号,如果INF文件显示Class=Ports;
    // 则由MsPorts!PortsClassInstaller创建
    status = WdfDeviceOpenRegistryKey(
        Device,
        PLUGPLAY_REGKEY_DEVICE,
        KEY_QUERY_VALUE,
        WDF_NO_OBJECT_ATTRIBUTES,
        &key);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    RtlInitUnicodeString(
        &portName, 
        L"PortName");

    // 注意,如果不手动初始化UNICODE_STRING结构,下面的很多函数都会失败
    RTL_INIT_UNICODE(comPort, 0, Buffer, COM_NAME_SIZE);

    status = WdfRegistryQueryUnicodeString(
        key,
        &portName,
        NULL,
        &comPort);
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    // 注意,如果不手动初始化UNICODE_STRING结构,下面的很多函数都会失败
    RTL_INIT_UNICODE(symbolicLinkName, 0, NameBuffer, SYMBOL_NAME_SIZE);

    RtlUnicodeStringPrintf(
        &symbolicLinkName, 
        L"%s%s", 
        L"\\DosDevices\\Global\\", 
        comPort.Buffer);

    // 3. 创建符号链接
    status = WdfDeviceCreateSymbolicLink(
        Device, 
        &symbolicLinkName);
Exit:
    return status;
}

唯一需要注意的是,需要手动给UNICODE_STRING分配内存,否则函数会失败:

// 初始化unicode字符串
#define RTL_INIT_UNICODE(_obj, _length, _buff, _max_size) \
            {   \
                _obj.Length = _length;\
                _obj.Buffer = _buff; \
                _obj.MaximumLength = _max_size;\
            }
内核线程的创建

为什么这里使用内核线程,原因如下:

1. 如果使用定时器,那么代码可能运行在DISPATCH_LEVEL级,虽然可以创建使用PASSIVE_LEVEL的定时器,但是那样除了增加了难度外并无意义;

2. 如果使用工作项,那么需要单独为了退出工作项做出一些额外操作,工作项更适合那种在DPC级别创建的临时线程,不需要为其同步的,长时间驻留内核的线程用工作项意义并不大;

那还是使用内核线程吧!下面是内核线程的创建代码:

typedef struct _tagThreadContext
{
    PKEVENT     ExitEvent;
    PKTHREAD    ThreadObject;
    HANDLE      ThreadHandle;
}ThreadContext,*PThreadContext;

NTSTATUS CreateKernelThread(PThreadContext pContext)
{
    NTSTATUS status;

    pContext->ExitEvent = ExAllocatePool2(POOL_FLAG_NON_PAGED, sizeof(KEVENT), 'KBSU');
    if (NULL == pContext->ExitEvent)
    {
        return STATUS_UNSUCCESSFUL;
    }

    KeInitializeEvent(pContext->ExitEvent, SynchronizationEvent, FALSE);

    // 创建内核线程
    status = PsCreateSystemThread(
        &pContext->ThreadHandle,  // 返回的线程句柄
        THREAD_ALL_ACCESS,        // 线程的访问权限
        NULL,                     // 对象属性
        NULL,                     // 线程隶属的进程 (NULL 表示系统进程)
        NULL,                     // ClientId (不需要)
        ThreadRoutine,            // 线程入口函数
        pContext                  // 传递给线程的上下文参数
    );

    if (!NT_SUCCESS(status)) 
    {
        ExFreePoolWithTag(pContext->ExitEvent, 'KBSU');
        pContext->ExitEvent = NULL;

        return status;
    }

    // 解析对象句柄
    status = ObReferenceObjectByHandle(
        pContext->ThreadHandle,
        THREAD_ALL_ACCESS,
        *PsThreadType,
        KernelMode,
        &pContext->ThreadObject,
        NULL);
    if (!NT_SUCCESS(status))
    {
        PsTerminateSystemThread(status);
        pContext->ThreadHandle = NULL;

        ExFreePoolWithTag(pContext->ExitEvent, 'KBSU');
        pContext->ExitEvent = NULL;

        return status;
    }

    return status;
}

NTSTATUS StopKernalThread(PThreadContext pContext)
{
    NTSTATUS status = -1;
    if (pContext->ExitEvent != NULL)
    {
        KeSetEvent(pContext->ExitEvent, 0, FALSE);

        if (pContext->ThreadObject != NULL)
        {
            LARGE_INTEGER timeout;

            // 设置超时时间(例如 1 秒)
            timeout.QuadPart = ( - 10 * 1000 * 1000); // 1 秒,单位为 100 纳秒

            // 等待事件或超时
            status = KeWaitForSingleObject(
                pContext->ThreadObject, // 等待的对象
                Executive,              // 等待类型
                KernelMode,             // 运行模式
                FALSE,                  // 是否可警报
                &timeout                // 超时时间
            );

            if (status == STATUS_SUCCESS) 
            {
                ZwClose(pContext->ThreadHandle);
                ObDereferenceObject(pContext->ThreadObject);

                // 如果事件被设置,退出循环
                pContext->ThreadObject = NULL;
                pContext->ThreadHandle = NULL;
            }
        }
        ExFreePoolWithTag(pContext->ExitEvent, 'KBSU');
        pContext->ExitEvent = NULL;
        return status;
    }
    return status;
}

这里的代码看起来和应用层线程差不多,首先初始化内核线程和事件,然后在内核线程中等待退出事件,唯一不同的是,在内核中需要把引用解析为内核对象来等待。

对控制端点的读写

USB对控制端点的读写比较简单,但WDF在这里的封装也有问题,对于不同类型的USB命令居然仅仅采用不同的ID简单区分:

WDF_USB_CONTROL_SETUP_PACKET_INIT

WDF_USB_CONTROL_SETUP_PACKET_INIT_CLASS

WDF_USB_CONTROL_SETUP_PACKET_INIT_FEATURE

WDF_USB_CONTROL_SETUP_PACKET_INIT_GET_STATUS

WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR

如果看这些宏的实现,就会发现它们的差别非常细微。 

主要使用的是下面两个宏:

// USB类定义
WDF_USB_CONTROL_SETUP_PACKET_INIT_CLASS

// 厂商定义
WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR

使用同步传输即可,封装同步传输为下面的函数:

NTSTATUS SynchControlCommand(
                int type, 
                int Direction, 
                int request, 
                int value, 
                int index, 
                char* bytes, 
                int size, 
                PDEVICE_CONTEXT pContext)
{
    NTSTATUS                        status;
    WDF_USB_CONTROL_SETUP_PACKET    controlSetupPacket;
    WDF_REQUEST_SEND_OPTIONS        sendOptions;
    WDF_MEMORY_DESCRIPTOR           memoryDescriptor;

    // 1. 初始化内存区域
    WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, (PVOID)bytes, size);

    // 2.设置超时时间
    WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
    WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(3));

    // 3. 初始化请求类型
    if (type == VENDOR_COMMAND)
    {
        WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(&controlSetupPacket,
            BmRequestHostToDevice,       // Direction of the request
            Direction,                  // Recipient
            (BYTE)request,              // Vendor command
            (USHORT)value,              // Value
            (USHORT)index);             // Index 
    }
    else
    {
        WDF_USB_CONTROL_SETUP_PACKET_INIT_CLASS(&controlSetupPacket,
            BmRequestHostToDevice,       // Direction of the request
            Direction,                  // Recipient
            (BYTE)request,              // class command
            (USHORT)value,              // Value
            (USHORT)index);             // Index 
    }

    // 4. 同步发送请求
    status = WdfUsbTargetDeviceSendControlTransferSynchronously(
        pContext->UsbDevice,
        WDF_NO_HANDLE,               // Optional WDFREQUEST
        &sendOptions,
        &controlSetupPacket,
        &memoryDescriptor,           // MemoryDescriptor
        NULL);                       // BytesTransferred 

    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
            "WdfUsbTargetDeviceSendControlTransferSynchronously failed %x\n", status);
    }

    return status;
}

以上函数的参数解释如下:

  • type:请求类型
  • Direction:请求方向
  • request:请求码
  • value:值
  • index:索引
  • bytes:可能的缓冲区
  • size:缓冲区大小
  • pContext:设备上下文

 再封装读写模式的函数如下:

// 对0号配置端点进读操作
NTSTATUS ReadConfigPipe(int value, int index, char* bytes, int size, PDEVICE_CONTEXT pContext)
{
    return SynchControlCommand(VENDOR_COMMAND, DEVICE2HOST, CONTROL_COMMAND, value, index, bytes, size, pContext);
}

// 对0号配置端点进写操作
NTSTATUS WriteConfigPipe(int value, int index, char* bytes, int size, PDEVICE_CONTEXT pContext)
{
    return SynchControlCommand(VENDOR_COMMAND, HOST2DEVICE, CONTROL_COMMAND, value, index, bytes, size, pContext);
}
批量读数据

另外一个要解决的问题是数据传输,USB是主从模式,那意味着需要主机主动发送读请求来从设备读取数据,整理的函数如下:

NTSTATUS SynchBulkRead(char* bytes, int size, ULONG *pRetSize, PDEVICE_CONTEXT pContext)
{
    NTSTATUS status = -1;
    WDF_MEMORY_DESCRIPTOR  readBufDesc;

    // 1. 初始化缓冲区描述
    WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(
        &readBufDesc,
        bytes,
        size
    );

    // 2. 同步发送数据读取
    status = WdfUsbTargetPipeReadSynchronously(
        pContext->BulkReadPipe,
        NULL,
        NULL,
        &readBufDesc,
        pRetSize
    );

    if (!NT_SUCCESS(status) || (int)*pRetSize > size)
    {
        return status;
    }

    return status;
}

同步读也许存在隐患,但是现在先不管这个问题,毕竟同步改异步只是时间和精力问题。

INF文件修改

代码完成之后,还有两个问题,第一个是创建的是USB驱动类型,但是COM口使用的不是这个类型的;另外是生成的INF文件并未指定PID和VID,需要手动修改,我们需要对INF进行下面的修改:

; 1. 注释原先的USB类 
;Class=USBDevice
;ClassGuid={88BAE032-5A81-49f0-BC3D-A4FF138216D6}

; 2. 添加Ports类
Class=Ports
ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}

; 3. 注释原先的PID和VID
;%USBKmDriver.DeviceDesc%=USBKmDriver_Device, USB\VID_vvvv&PID_pppp USB\VID_067B&PID_2303

; 4. 添加PL2303的PID和VID
%USBKmDriver.DeviceDesc%=USBKmDriver_Device, USB\VID_067B&PID_2303

其它INF文件内容暂时不修改了,毕竟这个仅仅是为了调试。 

安装流程和测试

 

下面为运行的结果:

项目开源地址为: USB PL2303 Windows Driver 

标签:status,USB,WDF,windows,pContext,PL2303,之二,线程,NULL
From: https://blog.csdn.net/m0_72813396/article/details/144573227

相关文章

  • 2024最新工具分享 | 24年最新AWVS/Acunetix Premium V24.8高级版漏洞扫描器(最新版)Wi
    前言AcunetixPremium是一种Web应用程序安全解决方案,用于管理多个网站、Web应用程序和API的安全。集成功能允许您自动化DevOps和问题管理基础架构。AcunetixPremium:全面的Web应用程序安全解决方案Web应用程序对于企业和组织与客户、合作伙伴和员工的联系至关......
  • 「转」Windows端口转发管理脚本(支持ipv4+ipv6)
    将下面代码保存成.bat文件,[b]使用管理员运行。@echoofftitleWindowsIPv4/IPv6端口转发管理工具color0A:menuclsecho========================================echoWindowsIPv4/IPv6端口转发管理工具echo========================================echo1.查......
  • EasyPlayer.js视频流媒体播放器windows播放器多窗口播放音量的控制方法
    随着互联网技术的飞速发展和移动设备的普及,流媒体服务已经成为人们消费娱乐内容的主要途径之一。流媒体行业已经成为一个巨大的娱乐产业生态,各个环节都在不断发生着创新和变革。这些技术的发展不仅改变了内容的创作和分发方式,也为用户带来了更加丰富和个性化的体验。那么在实际......
  • Windows交叉编译MNN-3.0.0安卓版本库(通过WSL2)
    一、写在前面昨天写了在WIN10上面通过AndriodStudio来编译MNN的demo,通过解压.apk文件来得到libMNN.so文件。今天在记录一下使用WSL2来编译MNN-Andriod。二、WSL2的安装由于不是本篇的主题,故不在这里进行展开,后续会补充步骤链接。三、WSL2编译环境配置1、NDK-Linux安装上篇......
  • windows环境下 redis3.0 主从+哨兵,一站到底!
    redis主从+哨兵,一站到底。没有废话,全是干货简介:本文使用redis版本3.0+,版本不同时,配置会有些许差异。目的:嫌集群太大,用不上;嫌单机太小,有点low。。那就使用主从+哨兵老规矩:用上了,给个赞!!!搭建redis主从的文件结构,看红框一、主从配置1.1主从主从,一主两从。数量可以配置多......
  • 系统攻防-Windows&Linux&远程探针&本地自检&任意代码执行&权限提升&入口点
    知识点:1、远程漏扫-Nessus&Nexpose&Goby2、本地漏扫(提权)-Wesng&Tiquan&Suggester3、利用场景-远程利用&本地利用&利用条件一、演示案例-操作系统-远程漏扫-Nessus&Nexpose&GobyNessusNessus号称是世界上最流行的漏洞扫描程序,全世界有超过75000个组织在使用它。该工具提......
  • 【Nginx】Windows平台下配置Nginx服务实现负载均衡
    1.下载Nginx下载地址:nginx:download2.安装NginxPS:Nginx基本目录如下:●conf:存放Nginx配置文件的目录●docs:存放Nginx文档的目录●html:存放静态html文件的目录●logs:存放Nginx日志的目录●temp:存放临时文件的目录 2.1启动Nginxnginx相关命令:startnginx启动ng......
  • 保姆级·在Windows本地配置并运行Kafka
    目录一.下载并安装Idea:进入idea官网:IntelliJIDEA 下载最新版idea(安装及其他问题请参考其他大佬的文章)二.下载并安装JDK:1.进入idk官网:JDK 下载最新版jdk2.将jdk路径配置到windows环境变量下(JAVA_HOME),并将其添加到path中*配置环境变量具体方法:1.单击windows键​编......
  • Windows更新之后声音消失了?
    前言本人电脑在经过一次更新之后Realtek声卡驱动就出现了问题,问题具体表现如下1.音量标志存在。2.音量输出正常,音量调到近乎满格才会出现断断续续声音,在听音乐以及看剧时几乎听不到背景音乐的声音只能听到一些人声。解决历程最初我去b站尝试寻找解决办法例如:1.通过卸载......
  • Windows 记录开机后应用启动慢的问题
    【声明】CSDN只做转发不做时时更新,最新博客请关注博客园 Windows记录开机后应用启动慢的问题-唐宋元明清2188-博客园最近大屏产品经常报一些开机启动的问题,工厂反馈厂测软件有些模块测试不通过,家里开发测试均发现Launcher等软件首次启动需要加载10多秒。经过小伙伴们初......